# The ticket page Every ticket this skill works has a **ticket page**, and every epic has an **epic index**. The page is where Gib follows the work: the ticket as Jira has it, the plan, live progress, proof beside the mocks, and the review. It updates while you work, so a write you skip is a gap he sees. It is a personal working file, exempt from the prose bar like the rest of `.claude/docs/`. ``` .claude/docs/epics/ / index.html, epic.data.js # the epic index, in build order / index.html, ticket.data.js # the ticket page resources/ proof/ deliverables/ mr.md tickets// # same shape, for a ticket with no epic, and no index ``` Each `index.html` carries the renderer from `templates/site/` and its own data inline, rewritten on every write, so it renders alone with no server and no other file. That is what T3 Code's file viewer needs: it sandboxes the page, with no `fetch` or `localStorage`, and serves files only from the page's own folder and below. The `.data.js` beside it is the store `ticket-page` reads and writes. An open page polls it to stay live, and shows its last write if it cannot. Screenshots and attachments stay as relative files under the ticket folder. ## In T3 Code Gib follows the work in a T3 Code thread, so the page and the plan come to him there. - **The live page.** `ticket-page open ` refreshes the page and prints its absolute path. Link that path in your reply, and it opens in T3's file viewer beside the thread. Outside T3 (no `T3CODE_THREAD_ID`) it also opens the browser. Never start an http server for the page. - **Inline snapshots.** `ticket-page view ` prints a self-contained page styled with T3's theme. Pass its output, verbatim, to `html_preview`, check the screenshot, then to `html_render` with the `contentHeight` it reported (capped at 2000) as the height. Do not restate what the snapshot shows in your reply. Snapshots do not update, so render one at the moment it is for, not on every write. | Moment | View | |---|---| | Plan ready for approval (Phase 1, last step) | `plan`, then `mocks --mock ` once per artboard | | Starting pre-mr-review, final hand-off, and on resume | `progress` | - One artboard per render, so each can be expanded full screen on its own and options sit one after another for comparison. `view` refuses a page over html_render's 512 KB and names the artboards to render one at a time. - Keep artboards light: CSS and real copy, not base64 screenshots. A screenshot pasted into an artboard as a data URI is most of its size, and every byte goes through the conversation. ## Write through `ticket-page`, always `~/.agents/skills/ticket/scripts/ticket-page` is the only writer of the data files. It validates before every write and refuses a broken one, writes atomically so the open page never reads half a file, appends each change to the page's live log, and keeps the ticket's row on the epic index current. Editing a `.data.js` file by hand skips all four. `ticket-page help` lists every command and its flags. It finds the ticket by key under the current repo's `.claude/docs/epics/`, so run it from inside the repo. ## What to record, and when | Moment | Command | |---|---| | Issue fetched to a scratch path | `init --issue `. Prints the ticket folder, then any populated Jira fields the page does not show. Then `open ` and link the path. | | Attachments downloaded, a video transcribed, frames extracted | `scan ` | | Ticket branch resolved, ticket classified | `meta --branch --classified bounded` | | Research notes worth keeping: what a video says, what a spreadsheet holds, a check against prod | `note "" --file notes.html` | | Plan written | `plan <KEY> --file plan.json`, then one `mock` per artboard | | Plan approved, Phase 2 begins | `approve <KEY>` | | Starting a step | `step <KEY> <n> running` | | A step's commit exists | `step <KEY> <n> done --commit <sha>`, one `--commit` per commit | | A criterion is met | `criterion <KEY> <ID> done --evidence "<what shows it>"` | | Proof captured (image, mp4, or text), or dropped from `proof/` | `proof <KEY> --file proof/<name> --proves AC1,AC3 --caption "<test case>" [--mock <id>]`, `proof <KEY> --test "<name>" ...`, `unproof <KEY> <file>` | | Council finding, and its resolution | `finding <KEY> add "[should] <text> (<seats>)" --severity CONFIRMED --where <file:line>`, then `finding <KEY> F1 fixed --commit <sha>` or `rejected --reason "<why>"` | | pre-mr-review audit read | `verdict <KEY> <verdict text> --recommendation <R> --head <sha>` | | A Jira field PUT and verified | `jira <KEY> "<field name>"` | | `mr.md` written | `mr <KEY>` | | Anything else Gib would want to see happen: suite green, a surprise, a blocker | `event <KEY> "<text>"` | A step is done when its commit exists and `ticket-page show <KEY>` lists that commit beside it. `step done` refuses without `--commit`. A step with nothing to commit, such as a verification pass, takes `--no-commit`. ## `plan.json` The plan is one JSON file, written with the Write tool and loaded with `ticket-page plan`. Prose fields are HTML fragments: `<p>`, `<b>`, `<code>`, `<ul><li>`. `plan` replaces any earlier plan and clears its progress and proof, because a rewritten plan is a restart. To change a plan mid-build (a new step, a reworded criterion), print it with `plan-json <KEY> > plan.json`, edit that, and load it with `plan --amend`: steps whose number and text are unchanged, and criteria whose id remains, keep their progress, and proof keeps the criteria that still exist. ```json { "criteria": [ { "id": "AC1", "text": "Every company can carry a five character canonical ID.", "steps": [1, 2] }, { "id": "AC2", "text": "A duplicate canonical ID is rejected with a readable message.", "steps": [3] } ], "questions": [], "approach": "<p><b>Company.canonicalId</b> is the only new state. The engagement ID is derived by <code>formatEngagementCanonicalId</code>, so the two cannot drift.</p>", "decisions": [["Engagement canonical ID", "Derived at read time", "Matches the spreadsheet formula, with no second column to backfill."]], "steps": [ { "n": 1, "text": "Add the canonicalId module, tests first.", "tdd": true }, { "n": 2, "text": "Schema, migration and backfill." }, { "n": 3, "text": "Map the unique violation to CONFLICT.", "tdd": true }, { "n": 4, "text": "Canonical ID field on the company form.", "kind": "mock" } ], "risks": [ { "risk": "Two companies given the same code", "handling": "The unique index blocks it and step 3 returns a readable CONFLICT.", "criteria": ["AC2"], "fromTicket": true } ], "tests": [ { "kind": "integration", "text": "companies.update with a taken code throws CONFLICT.", "criteria": ["AC2"] } ] } ``` - Steps are numbered 1 to N in order. `kind` is `code` (the default), `mock`, or `deliverable`. - Every criterion names the steps that satisfy it. Risks and tests name the criteria they cover. - A risk or test that comes from the ticket's own table carries `"fromTicket": true`. Your own additions leave it off, so the page shows which rows the ticket asked for. - `questions` holds only what genuinely needs Gib's answer, each as a direct question. Empty means none. ## Mocks When a step changes what a user sees (a new screen, a changed layout, a new control, copy a user reads), the plan carries mock artboards: ``` ticket-page mock <KEY> <id> --title "<screen>" --criteria AC3,AC8 --note "<what the build will not do>" --file artboard.html ``` - An artboard is a complete small HTML document with its own `<style>`, rendered in a sandboxed frame beside the proof. Draw the screen state the plan introduces, with the app's real navigation and density around it rather than a wireframe of boxes. - One artboard per new screen state. When the direction is Gib's call, draw each candidate as its own artboard (`option-a`, `option-b`) and ask the choice in `questions`, naming the artboards. This is how the house rule of several static mocks before touching real components is met inside the ticket flow. - `--criteria` names what the artboard illustrates. The Progress tab shows it under those criteria, and replaces the empty half with the proof once a `proof ... --mock <id>` lands. - `--note` states what the artboard shows that the implementation will not do. - A spike deliverable's polished mocks (see **Mocks and datamodel changes**) can start from the approved artboard. The built screens are still screenshotted for real. ## The epic index The index lists the epic's stories in build order: the order they should be completed in, which is not key order and not always the Jira link graph. Each worked ticket keeps its own row current. The rest of the index is yours to seed and keep true. - **Seed it** the first time a ticket under the epic is worked, and refresh it on later runs. Search the epic's children (`parent = <EPIC> ORDER BY rank`). For each, record the title, the estimate as `--hours`, the Story Risk Level as `--risk`, the stories it is blocked by as `--after`, and `--status done` if Jira says Done: `ticket-page epic <EPIC> story <KEY> ...`. Closed spikes go in with `epic <EPIC> closed <KEY> --title T`. - **Set the order** with `ticket-page epic <EPIC> order <KEY> <KEY> ...`, listing every story. If the epic already records a build order (a `summary.md` or plan in its folder), that order wins. Otherwise order by the blocked-by links, then Jira rank. Write the reasons the order is what it is with `epic <EPIC> why --file reasons.txt`, one reason per line. - **Gib reorders by asking.** When he does, rerun `order` and update `why`. - A story is marked done only by `--status done`, once it has merged or Jira says Done. The index never guesses that. ## Reading it as an agent `ticket-page md <KEY>` prints the ticket and the plan as markdown. Use it when you resume a ticket, and write it to a scratch file for the council and the audit, which read it there. ## Tickets from before the page Tickets planned before the page existed have `resources/ticket.md` and `plan.md` and no `ticket.data.js`. Leave those files alone. When Phase 0 resumes such a ticket, convert it first: `init` from `resources/issue.raw.json`, `plan` from the plan's content, then `approve` and `step ... done` for each step `git log` shows committed. From then on the page is the record.