The graph reads the existing GET /api/orgs/<id>/workflows; no new route and no write.
A stopped bus shows the data-ws-unread note, never "no workflows". An organization
with none says so and links Apply a pack only where this Console may apply one.
At 700px and below the graph runs top to bottom inside its own frame.
The Dashboard's tiles are Agents working of all agents, Organizations running, Needs you, Open tasks,
Done this week and Projects, each added up only from the organizations whose reads gave it; a tile
names every organization it leaves out and why. The Sessions and Model spend tiles go: the first is
the Agents tile now, the second had no figure. An organization's page replaces its KPIs and Metrics
placeholders with Agents working, Open tasks, Needs you and Done this week for that organization,
each a link to the system page filtered to it, from one cross-organization read scoped to it. The
system Agents page's row building is shared so a tile's figure is that page's row count.
- assistant.css: the pane title shrinks before the header tools wrap, so the close button stays on the title's line at 1440.
- assistant.js: paintNote draws the line under the lead; a refusal marks it data-refused and the line shows in the warning colour until the next change clears it.
- assistant-threads browser test: the refusal line carries data-refused, and closing a pane clears it.
- A system page with nothing above it in the trail hides the trail that repeats its h1; drill-in trails stay.
- A project's thread link and each open-in-chat link add that thread as a pane on #/assistant. With three
open, the first pane without an unsent message is replaced; when all three hold one, nothing is
replaced and the page says why. Per-org chat addresses still work typed or pasted.
- "Messages in <org>" moves from the bar into each pane header as a link.
- The pane composer's Resume assistant is a small secondary control; behaviour unchanged.
- Inbox class chips read the one map of decision classes to words.
- Removes the unused pane, idPrefix and views params from projects.js and workspace.js; fixes the stale
comment on the held-message status line.
- While an organization's bus is down, the header draws no bus control; the down banner's restart is the one.
The held draft goes back only into an empty composer and is cleared only once it is in it. A thread
that reads without a composer shows the held message in the pane's line. Only a stopped or
unavailable bus says the message comes back when the pane reads again.
A pane whose organization's bus is stopped or unavailable no longer polls or offers Refresh; it says
nothing is read until the bus starts again, holds any typed draft, and restores it untouched when the
bus returns. Unsent pane drafts now count for the organization-switch and Console-restart guards.
The Assistant sidebar item drops the Messages unread badge. "Start thread" is the primary action.
Tests cover the stopped pane's draft, a reply arriving with no refresh, and an uppercase show id.
The sidebar Assistant opens one system page, #/assistant. Each pane is the
chat thread view of its own organization: it reads, sends and updates
through that organization's routes, with a header carrying the title, the
organization chip, the project chip and a close control. "Open thread"
lists the threads of every answering organization, grouped by organization,
and starts a new thread in a chosen one. The open set and its order live in
the hash (open=org:id,...&show=org:id), validated on read. A stopped
organization is named at the top and in its own pane; the other panes keep
working. At 390 one pane shows with a tab strip.
Side by side is removed (panes.js, panes.css and its test); its old address
lands on the Assistant page.
The scope's org= in the workspace read is where the board reads, not a filter, so the
count line and the empty state no longer treat it as one. Found in the retaken shots.
- The card's organization chip carries the avatar in place of its dot; the bare avatar
before it is gone (prototype shot 13).
- A filter naming an organization or project that is not on the list is dropped from the
address after the render, and a dropped project reads again without its server filter.
- #/orgs/<id>/tasks with an id no organization can have says "This organization is not
on your list." instead of showing every organization's tasks.
- The write check's comment and the test's fake say orgSetup is Console-wide today.
#/tasks is a system route that shows every organization's tasks on one board,
with the per-organization columns, organization and project chips on each card,
and the organization and project filters kept in the address. An organization
that did not answer is named first with its reason. A move or edit goes to the
row's own organization and is offered only where that organization's own config
takes task changes. #/orgs/<id>/tasks is the same board scoped to one
organization, and a card opened from the system board links to its task there.
A render replaced by a newer one now draws from that newer read instead of
throwing on an empty board.
- Open on #/agents goes to #/orgs/<id>/agents?agent=<key>; the scoped table puts that agent first,
marks it (aria-current) and draws no Open or Actions column (review F1, F2).
- Fire's aria-label, confirm group and receipt use the organization's label (F5).
- A Fire sent to a named organization keeps its receipt after the opened organization changes (F7).
- Cancel moves focus back to the row's Fire (F10).
- agentsPage refuses a scope that is not an organization id instead of listing every organization (F11).
#/agents lists every organization's agents in one table: name, role, state,
what each works on, organization and project chips. Org and state filters
live in the hash query. A stopped organization is named at the top. A row
opens the agent in its organization; Fire is drawn only where that
organization's own config lets this Console decide, and posts to that
organization's hire route. #/orgs/<id>/agents leads with the same table
scoped to one organization, above the levels grid and held roles. A hired
agent reads as Hired (or Hired from pack X) with its org:hire id as
secondary text. The sidebar and dashboard Agents links open the system page.
#/inbox lists every answering organization's decisions from the one GET /api/workspace read, newest
first, each row with its organization chip and a project chip when it has one. Organization and
type filters live in the hash query. A stopped organization is named at the top with its reason.
An answer from the list reads and posts to the row's own organization (/api/orgs/<org>/...), never
the one opened last, and each item can be opened in its own organization. #/orgs/<id>/inbox is the
same list filtered to that organization. No server route was added.
- #/orgs/<id> with no page and no trailing slash now opens #/orgs/<id>/organization instead of the
system dashboard.
- The system dashboard resolves its own appearance (this browser's choice made
there, else the default) under mosaic-console-appearance-system; it no
longer shows the theme of the organization opened last.
- A stopped organization's page shows one bus banner: the page's own down
banner now carries the restart action, and the shell's "Bus unavailable"
banner hides on that page (display.js paintBus and app.js busDown).
- The dashboard row for a stopped organization no longer repeats "the bus is
not running" under "bus stopped"; the last start time still shows.
- Organizations table figures keep to one line (white-space, no font change).
- The Active agents status cell was class "st", which picked up the global
.st::before "?" glyph; it is now ws-st.
- Tests: red-first browser tests for each item, plus the test sp1 lacked: a
read still in flight at an organization switch keeps nothing.
- org-switch-browser: a stopped organization's page asserts one banner; the
shell's "Bus unavailable" is checked on its tasks page instead.
The second review found two paths the first fix round left open.
- The project-file save captures orgEpoch before its POST. When a switch
happened meanwhile, it neither paints nor announces the old file's
outcome on the new organization's page. An opaqueredirect still signs out.
- A live read (reader, get.api), the palette's inbox and tasks reads and
the board poll keep their answer in `last` only while the organization
they were sent for is still open. A later cached render on the new
organization no longer draws the old one's rows.
single-pane test 11 holds acme's file save across a switch to beta and
asserts nothing is announced: red on the previous bus.js ("Saved as commit
cccccccccccc."), green now. The kept-read guard has no browser test.
- First run (no organization yet) keeps #/ as its board; the router no longer rewrites it to
#/dashboard, which first run cannot draw.
- An organization switch stops the agents section's request poll and clears what it held. A
route-change restart chain finishes on the organization it started in.
- A write in flight at a switch drops its answer: an org epoch in bus.js, checked after each
await. The switch clears every busy flag, receipt and reply, and the doc drafts.
- The bus banner reads org state again only when the route crosses /dashboard, not on every
hashchange inside one organization.
- The down banner does not repeat its heading as the reason.
- Tests: first run wordmark, poll stop on switch, restart pinned to its organization, settings
save in flight at a switch, banner without a repeated reason. The org-switch test comment and
wait match the in-place switch.
#/dashboard is the system landing route: one row per organization with its bus
state, agent count (organization files), project count and needs-you count,
each only when a read gave it, and active agents. Opening an organization from
the sidebar or a dashboard row is a route inside the same page: no reload, a
Dashboard / <organization> breadcrumb, and later /api reads scoped to it through
the existing fetch wrapper, whose organization is now a variable the router sets
(mosaic-org-switch event). #/orgs/<id>/... stays the address. An organization
whose bus is down says so on the dashboard and on its page, with the reason the
workspace read gives.
The WebUI packs receipt passes routeFrom through on each hire line; the WebUI
README and TOOLS.md name it. The starter packs design doc gets a dated note
for the 2026-11-14 route ruling. New bus tests: a member pack hire on the
assistant route marks hired and awaiting-owner lines routeFrom assistant, and
a project pack with no chief route hires into the project on the assistant's
harness and model.
pack.state gave a pack hire only the chief of staff's resolved route, so an
org whose chief has no model refused every hire pack-hire-route-missing. The
route is now the chief's (launch.by), else the org assistant's (the instance
whose definition is assistant; the one named assistant when there are several,
else the first in key order), else null. Both resolve through resolveInstance,
need harness and model, and carry thinking when set. pack.state adds routeFrom,
and each hired or awaiting-owner receipt line carries it. Refusal next step,
CLI hint, WebUI hire reason and the bus, CLI and TOOLS docs say the refusal now
means neither has a route.
Review pd-review.md (changes 6). Fixed:
- P2#2: the owner's pack hire withdraws a pack-raised inbox decision only
after its replacement hire applied; a replacement that refuses (route
missing, prepare refusal) leaves the old decision open in the inbox.
- P3#3: an apply whose hire loop throws after the apply committed still
answers the apply receipt, with no hires and the code as hireError; the
route passes a code through and the card says the hires did not run.
- P3#5: the packs read marks an awaiting-owner line this pack's own hire
raised fromPack: true; the owner's card counts it in "Hire the N".
- P3#6: one pack hire loop per business; a second refuses
organization-busy and raises nothing.
- P2#1 docs: TOOLS.md and the bus README say no member surface calls
pack.hire.console yet (the member surface is a recorded follow-up).
Deferred: P3#4 project agents naming their pack (projectUnion is shared
with the runtime).
Ruling 2026-11-14: a person's pack apply records one hire decision per
proposed hire and applies it in the same act, through the hire-decisions
path, with the evidence an inbox approval leaves.
- bus: pack.apply.console runs the pack hire loop; pack.hire.console hires
an applied record's open proposals. The owner's apply approves flagged and
credential lines; a member with org.hire gets the unflagged hires and the
rest open as awaiting-owner decisions; an agent apply still refuses
human-required. Project packs hire into projects.<p>.agents. Nothing is
hired twice. Credential lines are approved, not bound. New refusal codes
pack-not-applied and pack-hire-route-missing. The instances read names the
pack each hire came from; the packs read lists hired agents and open
proposals with reasons.
- organization: pack.hire.console maps to org.hire; instances carry
hireDecision.
- cli: mosaic org pack hire ID PACK [--project P]; apply prints the hires.
- webui: pack card shows N agents hired, open proposals with reasons and
"Hire the N proposed agents"; Agents and organization pages redraw on
mosaic-org-changed; agent cards say "from pack X"; Fire (not on the chief
or assistant) removes the card at once with a receipt.
- docs: design doc section 3 dated note, TOOLS.md, package READMEs.