issues-view¶
The dashboard's Issues surface as GitHub-style ROUTED pages sharing [[review-chrome]] — a ListView query + Open/Closed sections + real issue facets over structured anchor rows, a standalone detail page whose thread writes route to the issue's own store, and a standalone compose page at
raw source¶
Issues are one object over every store (issues), and a human wants one place to read them — an
agent's local taste issue and a GitHub issue on the same node belong on the same page. The human's
directive names the navigation model: GitHub's own issues UI, verified live — a LIST page whose whole
state lives in its URL, rows that are plain copyable links, a click that PUSHES history onto a standalone
full-page DETAIL, and browser Back that restores the exact filtered list. The earlier master-detail split
pane is gone; so is any in-page selection echo. Writing an issue is a place too: a cramped pop-out over
the list said "this is a small aside", when opening an issue is one of the two things a human does here —
so New is a routed page with the same standing as reading one, and it writes through the app's own
writing surface instead of a form-shaped copy of it. The dashboard stays a thin window over the CLI's
truth: it renders what /api/issues returns, computes nothing, and every write goes through the SAME
verbs the CLI uses.
expanded spec¶
- Three pages, one route family — the shared review-chrome.
#/issuesis the list page (the side-nav rail entry and ⌥4 land here);#/issues/<id>is the detail page;#/issues/newis the compose page. All three are bookmarkable, reloadable, directly openable.newis the ONE path word the family spends on something that is not an issue id, so the local store RESERVES it at id minting (local-issues) — an issue can never own an address the compose page answers, and the reservation is a visible numeric-suffix collision, not a silent shadow. Rows are REAL anchors to their detail address; clicking one is a normal hash PUSH, and Back returns to the list URL with its query intact. All list state is review-chrome's ONE token query (is:issue state:openby default; the review-query engine): a human's edit, tab, or menu pick PUSHES the canonical address — bare#/issuesfor the default view, exactly?q=<raw text>otherwise — and the list re-derives its whole state from the URL on every hashchange, so Back replays text, page-address form, rows, and scroll exactly. Page is the shared review-chrome hash-query view state: initial/filter reset omits page; pagination to page 1 writespage=1; page followsqwhen present; direct open/refresh/Back preserve the explicit form. Legacy structured params (state/concluded/store/author/node/live/q) replay at the route layer as a REPLACE into that token text; old deep links keep working and the old shape is never re-minted. The list reads the ONE server paged-review contract after merged-store source selection and filtering; it never downloads the merged issue population to filter or slice in React. A detail address naming no issue renders the shell's honest not-found with a link to the list. Esc routes nothing (side-nav). - One merged list, store-tagged — RESIDENT, never cold-fetched. The source is issues's
mergedIssues— which excludes eval-remark threads (eval-issue-split: a scenario-scoped concern is a remark and lives on the Evals pages). The rows are the backend's resident snapshot, requested one 25-row slice at a time; freshness is PUSH-first (remark-substrate write-visibility): the list re-requests when the board's issue-freshness stamp moves — together with the source-session presence set, the one other board input its answer depends on — with the 15s cold lane as the fallback, equal revision repainting nothing, and the viewer's own write forcing the read. It keys that stamp and never a board value that merely churns per frame: a proxy like that is fresh only by luck, and goes quiet the day the churn is optimized away. Rows render in API order — stores interleaved newest first, no salience ranking. Bare query words search the concern/id/originator/node facts; the metadata header's Open / Closed sections + counts are the lifecycle switch — token surgery onstate:only, every other token preserved, counts computed under the rest of the query (default Open; every non-open state belongs to Closed, and a concrete concluded spelling likestate:landedmatches that status honestly). Matching and options come from the review-filters Issue adapter — page code only bridges the parsed token text into that engine. Menus exist only for the low-cardinality data the model actually has: the store pick and the source-session presence facet (session:present|missing— live-session-filter); store stays directly reachable at 390px while presence lives in review-chrome's semantic secondary Filters menu — filter/funnel + localized text - chevron, never a kebab/action affordance. Its stable active-group count reads the presence token.
At 390px an active Store face condenses visually to the selected store while retaining its fully
qualified accessible name, so Open/Closed, Store, and Filters never overlap. Originator and spec node are HIGH-cardinality:
author:/node:tokens, hand-typed or completed from the input's bounded inline autocomplete — no enumerating dropdown; an unknown or historical value still submits and yields the honest filtered zero. An ACTIVE menu value whose data option disappeared keeps its cheap All off-switch — and the visible text is always the canonical release — so data disappearance cannot trap the list behind an invisible filter. New remains the page-title action. No assignee/labels/project theatre is invented for a model that has none. An actually empty issue store says there are no issues yet; a non-empty store reduced to zero by section/query/facets instead says this view has no matching issues, through review-chrome's shared empty-state contract. - The row leads with the issue, never its plumbing. A structured two-level row: the status mark (GitHub
Primer's 16px
issue-openedocticon in the semantic open green; every concluded state — locallanded, forgeclosed— theissue-closedring+check in the one closed purple; never a CSS dot), then the wrapping concern; under it the real issue identity, originator, and opened time; at the right the comment count and store/node facts that exist. At 390px those facts join the secondary line and the title may wrap without horizontal overflow. The store is metadata, never identity: it never leads a row and never sits on a title. - The detail page is review-chrome's GitHub-grammar skeleton. Header: the concern ALONE as the
title, led by the shared compact back anchor — a REAL
#/issueshref derived from the canonical address (address-routing's one back helper), neverhistory.back, identical on a pushed visit and a direct open; browser Back still restores the exact filtered list. Status band: the same shared issue-state primitive as the list mark, now with its label. MAIN column: the markdown-rendered body (SpecBody — the one spec dialect, no raw##/pipes) then the reply thread, with the composer docked at the column's foot. SIDE rail — every value through review-chrome's ONE SideValue metadata primitive (min-width:0 shrink, single-line ellipsis, full text on the tooltip; information type explicitly labeled, never guessed from a bare token): the issue's OWN id under a localized Issue label (the full slug, truncatable — a bare#slugreads as a node), the store tag, the ORIGINATOR + liveness (a local thread'sbyis a session id — a live one is a click-through chip to#/sessions/<id>, painted by the board's STATUS_COLOR join; a forge login stays a plain labeled value), the spec-node refs under their localized label (click focuses the graph), and a forge permalink labeled with the store's concrete display name ("Open on GitHub"/"Open on GitLab" — canonical display-name data, never a URL sniff, never the word "forge"; a local issue renders none). At phone width the side metadata reflows ABOVE the body in the one column. A forge issue's comments render as the SAME reply thread a local issue gets — store never changes the thread's shape. The page is ONE addressed read, so its own address can tell it nothing about later writes: it re-reads on the same board issue-freshness stamp the list follows (remark-substrate), which is how a second party's remark reaches a reader already sitting on the thread. Only a new ADDRESS may wipe it to the loading face; a stamp tick re-reads quietly behind the painted thread. - A human writes from here — to the issue's OWN store. The composer is the ONE shared thread-composer
(event-detail docks the same component,
Thread.jsx): a quiet bordered container, a borderless writing surface floored at two lines that auto-grows through composer, the action row always visible — the@/**trigger buttons opening the shared [[mentions autocomplete, the host lifecycle actions, and an icon-only Send at the row's end; a failed send surfaces its error in that row. Close issue and Promote** live in that action row (GitHub's grammar — lifecycle acts on the conversation), each through the ONE store-routed verb; sign/accept/reject are not product verbs. Replies post as'human'viareplyIssue— a local reply git-commits, a forge reply posts a REAL comment — then the list refetches. A reply that is a REMARK gets its resolve/retract verbs (remark-substrate); a▶m:ss · stepfirst line is a time anchor; attached blobs render through the one shared evidence renderer. An@sessionin any composer is a passive mentions reference retained in the posted prose; it produces no notification or worker side effect. - New is a PAGE —
#/issues/new, GitHub's compose grammar. The list's New is the page-title action and a REAL anchor into that address: a click is the same hash transaction the address bar produces, so middle-click/new-tab/copy-address come free and the page itself survives a reload, a bookmark, and a pasted link. It is not a pop-out, not a dialog, and nothing on the page carriesrole="dialog". The page IS review-chrome'sDetailShell— the compact back anchor leading a localized title header, ONE main column, ONE metadata rail, the same geometry and phone reflow the detail page gets — so compose and read cannot drift into two skeletons. MAIN column: a labeled one-line title field, then the labeled description written in the SAME composer surface every other writing box in this app uses (a quiet bordered container, a borderless auto-growing textarea floored at a page-sized height, the persistent action row carrying the mentions@/**doors through the ONE shared trigger-insert mechanism) — New is never a second dialect of "an input". A Write/Preview switch renders the draft through the SAMESpecBodythe detail page renders the stored body with, so what the writer proofreads is what the issue will look like; an empty draft says so instead of previewing blank. SIDE rail: the compact store picker naming each store's canonical label exactly once — the one place the rail takes INPUT rather than showing a value — over the spec nodes the prose already links, derived live from the draft's[[id**grammar. That derived list is why no separate node-ids field exists: a**node**in the prose IS the node link (local infersnodes:, a forge post writes theSpec:marker from the same prose), and the rail shows the link being made instead of stating a rule the writer cannot see. On a page the autocomplete opens downward under the caret — no pop-out boundary to clear. Enter in the title and ⌘/⌃⏎ in the description both submit; a refused write surfaces its error in the action row. Create lands on the issue it just made: the response's id is navigated to with REPLACE (side-nav: automatic state-naming replaces), so the spent compose address leaves no emptied form in history and Back returns to the list. Cancel is the SAME derived list anchor the back anchor uses, neverhistory.back; Esc routes nothing. - Issue cards enter this page, never the forge. Every compact card in the node Issues tab is the SAME
IssueCardwhose canonical href is#/issues/<issue-id>; a forge permalink is detail-side metadata only. Long content clamps inside the card. - Honors the switch. When the issues workflow is OFF (
enabled: false), the page shows the muted "off" state — the dashboard reflects the one source of truth.