Skip to content

Commit 4fb00a0

Browse files
committed
Install SDD: next slice of per-file specs
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CSM4FVUbnP4PJJ9sawMfLb
1 parent 1d20c38 commit 4fb00a0

6 files changed

Lines changed: 248 additions & 0 deletions

File tree

Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
The dashboard's shell: it decides which page the user is looking at, keeps the live data every page shares flowing, and raises the alerts that belong to the whole app rather than to one page.
2+
3+
## User story
4+
5+
- The user opens the dashboard and lands on the Overview, moves between projects, agents, tickets and settings, and expects every one of those places to be a link they can bookmark, reload, share, or reach with the browser's Back button.
6+
- The user leaves the dashboard in a background tab and expects the tab itself — its title and its icon — to tell them when an agent is working and when something needs them.
7+
- The user expects to be told when the daemon has stopped answering, instead of staring at a page that silently stopped updating.
8+
9+
## Business logic — TL;DR
10+
11+
- **The URL is the selection** - which project, which agent, and which page are read from the address, so the app can never disagree with itself about what is on screen.
12+
- **One frame, three columns** - the agents-and-projects sidebar, the main pane, and the right rail, with the main pane swapped per route.
13+
- **Live and finished look the same** - a running agent and a finished one are shown in the same frame, so an agent ending changes what the frame says without the page rebuilding.
14+
- **One live stream, shared** - the selected agent's event stream is opened once by the shell and read by both the main pane and the right rail.
15+
- **The just-started agent** - a freshly started agent is shown immediately, before the daemon has written anything about it.
16+
- **The picked context is per project** - the file selection shared by the launcher and the rail's file tree, cleared when the project changes and when an agent is started with it.
17+
- **Cross-project polls live in the shell** - interventions, projects, and recent agents are polled once here and shared by everything that shows them.
18+
- **Notifications** - a browser notification when something lands on the "needs you" queue, and one for the new-activity feed, each only when the user has switched that category and browser delivery on.
19+
- **The tab tells you what is happening** - the browser tab's title carries the selected project and the needs-you count; its icon says whether an agent is working.
20+
- **A dead daemon says so** - a banner states that the daemon is not answering and that the page is frozen until it returns.
21+
- **Dead ends are pages, not blanks** - an address naming a project or an agent that does not exist renders an explanation with a way out.
22+
23+
## Business logic
24+
25+
### The URL is the selection
26+
27+
#### User story
28+
29+
The user wants to paste an agent's address to a colleague, reload without losing their place, keep two agents open side by side, and have Back return them to where they launched from.
30+
31+
#### Business logic
32+
33+
The address alone says what is on screen: the Overview at the root, a project's home and launcher under the project, one agent under that project, plus the settings page, the tickets list, one ticket's page, and that ticket's plan page. Every navigation the shell offers — picking a project, picking an agent, opening a ticket, going to settings — is an ordinary history entry, so Back and Forward work throughout, and reopening the dashboard returns to the project the user was last in.
34+
35+
#### Rationale
36+
37+
Which agent was in play used to be several independent pieces of state — the selected agent, the just-started agent, and a follow-the-live-feed flag — reconciled every time the screen was drawn, and they repeatedly disagreed about which agent the screen was about. An address cannot disagree with itself.
38+
39+
### One frame, three columns
40+
41+
#### User story
42+
43+
Wherever the user is, they want the same way back to their agents, their projects, and the Overview.
44+
45+
#### Business logic
46+
47+
Every route is drawn inside the same frame: the sidebar listing the project's agents (or, on the Overview, recent agents pooled across all projects) together with the projects list and the global navigation; the main pane; and the right rail with the file tree and the agent's views. The tickets page and the settings page take the whole main pane, and the tickets page drops the right rail entirely. On a project's launcher the rail's documents move into the main column, since the launcher has room for them; an agent's own page keeps the full rail.
48+
49+
### Live and finished look the same
50+
51+
#### User story
52+
53+
The user is watching an agent work and it finishes. Nothing should jump, reload, or be lost.
54+
55+
#### Business logic
56+
57+
A running agent and a finished one are rendered by the same view; only whether it is live differs. The action bar, the event feed and the message composer change what they offer as the agent ends, without the view being rebuilt around the user.
58+
59+
### One live stream, shared
60+
61+
#### User story
62+
63+
The user watches an agent's output in the main pane while its browser preview or one of its views is open in the right rail; both must show the same agent at the same moment.
64+
65+
#### Business logic
66+
67+
The shell opens the live event stream for whichever agent the address names and hands it to both the main pane and the rail, so there is one stream rather than one per panel. The rail's views are scoped to the agent's newest stretch of work, while the feed keeps the whole journal — so resuming a finished agent appends to the transcript instead of blanking it.
68+
69+
### The just-started agent
70+
71+
#### User story
72+
73+
The user types a prompt and presses Start. Something must happen at once, even though the daemon needs a moment to spawn the agent and write its first status.
74+
75+
#### Business logic
76+
77+
On Start the sidebar immediately shows a row for the new agent carrying the prompt the user typed, and the app navigates to that agent, whose live output streams before its status record exists. An agent missing from the project's list is treated as still starting when it is the one just started or when the list has not been read yet; only an agent genuinely absent from a list that was read is reported as gone.
78+
79+
A project with no git checkout gets no worktree, so starting there hands back no agent to navigate to. In that one case the app lands on the project, follows the live output, and adopts the running agent as the selection as soon as the project's agents are read — a correction rather than a step, so it does not add a history entry the user would have to press Back through. Any explicit choice by the user — picking an agent, switching project, starting a new agent — ends that follow.
80+
81+
Starting an agent can also happen from places where no project is selected, such as the onboarding checklist on the Overview and on the settings page, so the project the agent started in is named rather than assumed to be the selected one.
82+
83+
#### Rationale
84+
85+
The one-agent-at-a-time guess is only safe in the no-worktree fallback, because the daemon allows a single agent per project there. It is the only place in the app where the selection is inferred rather than read from the address.
86+
87+
### The picked context is per project
88+
89+
#### User story
90+
91+
The user picks the files an agent should focus on, from the launcher's file chips or from the rail's file tree — the same selection, whichever surface they use.
92+
93+
#### Business logic
94+
95+
The shell owns the picked context so both surfaces share one selection. It is cleared when the project changes — including when the change comes from Back or Forward rather than from a click — and again once an agent has been started with it, so the next launch starts from a clean focus.
96+
97+
### Cross-project polls live in the shell
98+
99+
#### User story
100+
101+
The sidebar badge and the Overview card both show how many things need the user; they must agree, and asking the daemon twice for the same answer is waste.
102+
103+
#### Business logic
104+
105+
The shell polls the cross-project reads once and shares them: the "needs you" queue on a slow cadence because it costs the daemon a GitHub query per project; the registered projects, whose per-project problems appear and clear on the daemon's own schedule and drive the sidebar's warning dot and the project banner; the selected project's files, scoped to the selected agent's worktree so files the agent creates appear without a reload; and, on the Overview only, the recent agents pooled across every project, since with no project selected the sidebar has none of its own to show.
106+
107+
The new-activity feed's only consumer in the browser is its notification, so it is polled exactly when that notification could fire and not otherwise.
108+
109+
### Notifications
110+
111+
#### User story
112+
113+
The user works in another window and wants to be told when an agent needs them — but only about the things they asked to be told about.
114+
115+
#### Business logic
116+
117+
A browser notification fires when a new item lands on the "needs you" queue, and, separately, for the new-activity feed of agents starting and finishing. Each requires both that the user switched that category on and that browser delivery is on. The notifier is told which projects the poll actually reached, so a project that could not be read is not mistaken for one whose items are all new. Discord delivery is not affected by any of this: the daemon delivers that itself.
118+
119+
### The tab tells you what is happening
120+
121+
#### User story
122+
123+
The dashboard sits in a background tab. Its title and icon are all the user can see.
124+
125+
#### Business logic
126+
127+
The browser tab's title carries the selected project's name and the count of things needing the user, so a backgrounded tab says which project wants attention. The tab's icon reflects whether an agent is currently working.
128+
129+
### A dead daemon says so
130+
131+
#### User story
132+
133+
The user's screen has stopped changing. Either their agent went quiet, or the whole page is stale — and those look identical.
134+
135+
#### Business logic
136+
137+
The shell checks whether the daemon answers at all and, when it does not, shows a banner across the top of the app saying that the daemon is not answering, that it is being retried, and that everything on the page is frozen until it returns.
138+
139+
#### Rationale
140+
141+
Without this verdict a dead daemon froze every surface silently: the live streams retry their transport without reporting anything and the polls keep their last value, so a quiet agent and a dead dashboard were indistinguishable.
142+
143+
### Dead ends are pages, not blanks
144+
145+
#### User story
146+
147+
The user follows a link to a project that was removed, renamed, or belongs to another machine; or to an agent whose worktree has since been retired.
148+
149+
#### Business logic
150+
151+
An address naming a project that is not registered renders an explanation and a way to the Overview — and only once the projects have actually been read, so it never flashes while the first read is in flight. An agent that is genuinely absent from the project's agents renders an explanation that an agent disappears when its worktree is removed, with a way back to the project. Deleting the agent that is on screen returns to the project and refreshes the sidebar, so its row is gone.
152+
153+
## Before modifying/creating SPEC.md files
154+
155+
You must always read and respect https://raw.githubusercontent.com/brillout/sdd/refs/heads/main/sdd.md
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
The gear menu beside a composer: where the user picks where the next agent runs and which of the global options are on. One dropdown replaces what used to be a row of loose checkboxes; every row writes the user's preference straight through, and the menu stays open so several can be flipped in one visit.
2+
3+
## Business logic — TL;DR
4+
5+
- **"Run on" chooses the run target** - one flat single-select list: This machine, GitHub Actions, Claude web, then the user's saved devices, then "Add a device…". Exactly one entry carries the checkmark.
6+
- **A device is a target, not a destination** - selecting a saved device makes it where the next agent runs; the browser stays on the local daemon, which relays the agent to that device. Only when the dashboard is genuinely connected to a remote daemon does that device carry the mark instead, and then "This machine" means "go back to the local daemon".
7+
- **Devices show whether they can be reached** - each saved device shows its address with an online/offline dot, dims when offline, and can be removed from its own row without selecting it.
8+
- **Global options are preference checkboxes** - each explains itself in one line, and a row that cannot be switched right now carries its reason in that same line rather than being a mystery greyed row.
9+
- **The gear says whether anything is on** - a dot on the gear when at least one option is active, and the exact count on hover.
10+
- **Where the target is already fixed, the menu says so** - a composer inside a running agent offers only the preferences, and its label reads accordingly rather than promising control over the run it does not have.
11+
12+
## Business logic
13+
14+
### Choosing where the next agent runs
15+
16+
#### User story
17+
18+
The user is about to start an agent and wants to say whether it runs on this device, on a fresh GitHub Actions runner, as a Claude Code cloud session, or on one of their other machines.
19+
20+
#### Business logic
21+
22+
The "Run on" submenu presents the three run targets — "This machine" (run here, as usual), "GitHub Actions" (a fresh runner), "Claude web" (hand off to a Claude Code cloud session, which opens its own pull request) — followed by every saved device and an entry to add one. The submenu's trigger shows the current choice, so the answer is readable without opening it.
23+
24+
Selecting a run target writes it as the preference and clears any selected device. Selecting a device makes that device the target instead, without navigating anywhere: the local daemon relays the agent to it and streams its events back. Because only one thing can be the target, a run-target row is marked only while no device is selected, so the single checkmark never doubles up. A selected device whose saved entry has since been removed reads as no device selected.
25+
26+
When the dashboard is connected to a remote daemon rather than the local one, that daemon's device is what carries the mark, and choosing "This machine" is the way back to the local daemon.
27+
28+
#### Rationale
29+
30+
"Claude web" is named for the hand-off it is rather than promising a streamed agent, because that agent runs on claude.ai and opens its own pull request.
31+
32+
### Saved devices
33+
34+
#### User story
35+
36+
The user has another machine running a daemon and wants to send work to it, drop it when it is no longer theirs, and know at a glance whether it is even up.
37+
38+
#### Business logic
39+
40+
Each saved device shows its name, its address, and a reachability dot: filled when the device answered, muted while offline or still being checked. An offline device is dimmed but still selectable. Each row carries its own remove control, which drops the saved device without selecting it. "Add a device…" explains what to paste: the address a machine prints when its daemon binds to the network.
41+
42+
### The global options
43+
44+
#### User story
45+
46+
The user wants to flip several options before starting an agent, and to understand why one of them cannot be flipped right now.
47+
48+
#### Business logic
49+
50+
Each option is a checkbox row carrying its name and a one-line description; hovering it gives the longer explanation. Switching one writes the preference immediately and leaves the menu open. When an option cannot be switched, its reason is appended to its own description line, because a tooltip does not open on a disabled row.
51+
52+
### What the gear itself says
53+
54+
#### Business logic
55+
56+
The gear shows a dot whenever at least one option is on and enabled — that some options are on is the signal worth carrying on the icon; the exact number is one click away and would be noise there. Hovering the gear names the menu and, when any are on, how many.
57+
58+
## Before modifying/creating SPEC.md files
59+
60+
You must always read and respect https://raw.githubusercontent.com/brillout/sdd/refs/heads/main/sdd.md
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
Every date, duration and countdown the dashboard shows, worded the same way everywhere.
2+
3+
## Business logic — TL;DR
4+
5+
- **A bad timestamp never reaches the user** - timestamps arrive unvalidated (an agent's start time, a ticket's date read out of its filename), so anything missing or unreadable shows as a dash instead of the browser's literal "Invalid Date".
6+
- **Three absolute forms** - the full local date and time; a short date and time without seconds, for places where the timestamp stands in as an agent's name; and the date alone for dense table columns.
7+
- **Age is always relative** - "22s ago", "30m ago", "5d ago", "2w ago", "1y ago": the Agents list dates every row this way, so a row reads the same whether it finished this minute or last year, with the exact moment available on hover.
8+
- **Freshness falls back to a date** - the at-a-glance boards read "just now", "12m ago", "3h ago", "2d ago", and past a week switch to the local date.
9+
- **Counting down never reads as late** - "in 4 min", "in 1 hr"; a scheduled moment the daemon has not reached yet reads "any moment", because it is imminent rather than overdue.
10+
- **Durations come abbreviated and spelled out** - "2s", "10m", "2h", "1d" for a figure, and "2 seconds", "10 minutes", "2 hours", "1 day" for a sentence explaining that figure.
11+
- **Ages and durations are floored, never rounded** - "1m ago" means at least a minute has passed, not "closer to one minute than to two".
12+
- **A quota reset is named by weekday** - "Tuesday 8:59pm", since the quota week shown above it already places the day; the full tooltip adds the date and names the time zone, so a user in another zone never has to guess whose clock the reset follows.
13+
14+
## Before modifying/creating SPEC.md files
15+
16+
You must always read and respect https://raw.githubusercontent.com/brillout/sdd/refs/heads/main/sdd.md
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
What the tests cover: a real timestamp renders as the viewer's local date and time, while an absent, empty or unreadable one reads as a dash — never as "Invalid Date" — and the caller may word that fallback itself ("no activity yet", "never").
2+
3+
Also covered: ages name seconds, minutes, hours, days, weeks and years, floored rather than rounded (a minute and a half reads as one minute) and staying relative even past a year; countdowns read "in 4 min" then "in 2 hr", and a moment already due reads "any moment" rather than as late; durations abbreviate to seconds, minutes, hours and days with no week unit, floor the same way, and never read below zero; the spelled-out duration pluralizes its unit for use in a sentence; and a quota reset names its weekday and bare time, with the tooltip spelling out the date and naming the time zone it is shown in.
4+
5+
## Before modifying/creating SPEC.md files
6+
7+
You must always read and respect https://raw.githubusercontent.com/brillout/sdd/refs/heads/main/sdd.md
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
The selected project's agents — both the live ones and the archived ones — refreshed every two seconds. The dashboard shell reads this list once and shares it, so the agent list and the main pane beside it always show the same set, and a refresh triggered by an action can never write a previous project's agents into the newly selected one. The shell also knows whether the first read has answered yet, which is what lets it tell an agent that is genuinely gone apart from one it simply has not read yet: a bookmarked link to an agent must not flash "gone" while the first read is still out.
2+
3+
## Before modifying/creating SPEC.md files
4+
5+
You must always read and respect https://raw.githubusercontent.com/brillout/sdd/refs/heads/main/sdd.md
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
What the tests cover: no agents are read until a project is selected; the selected project's agents refresh every two seconds; an explicit refresh shows a just-started agent without waiting for the next tick; switching project immediately empties the list rather than leaving the previous project's agents on screen, and a read still in flight against the previous project can never write its agents into the new one once it lands late; and a read that fails — a restarted daemon, say — keeps the last known agents instead of emptying the list.
2+
3+
## Before modifying/creating SPEC.md files
4+
5+
You must always read and respect https://raw.githubusercontent.com/brillout/sdd/refs/heads/main/sdd.md

0 commit comments

Comments
 (0)