Skip to content

fm/cf genui fleet - #1087

Draft
i098 wants to merge 68 commits into
mainfrom
fm/cf-genui-fleet
Draft

i098 wants to merge 68 commits into
mainfrom
fm/cf-genui-fleet

Conversation

@i098

@i098 i098 commented Nov 1, 2026 •

Copy link
Copy Markdown
Owner

Closes #1086

Base of the stacked PR #1088 (end-to-end fixes, issue #1085). Draft until the owner OKs the GenUI screenshots. Do not merge.

i098 added 30 commits October 29, 2026 10:35
Renders a spec over the stub WebSocket contract: cards move, resize, link
and pin; the header toggle picks agent wins or user wins; the input box
sends text to the agent; the sketch layer stays on top. A local mock
server stands in for apps/genui-server. Docs in docs/genui/canvas.md.

Closes #1020
Replace the three stub contract.ts copies with @crewship/genui-spec (src/wire.ts).
Server emit_view accepts the kit's STATE_SNAPSHOT event; the kit's intent
socket connects with ?role=agent so forwarded user intents reach it.

Closes #1017
…layout, tiling, link lines, camera and zoom, spread, pull-out, phone fit
…re-baseline sentrux for the new world module
Card chrome, Geist Mono, every view kind (decision, conflict, pipeline,
timeline, crew, shipped, plan, embed, incident, compare, ask, sys, tui),
live stamp, resize grip, hover ring and build-in motion (D13-D28).
renderCard(spec, component, widthTier) from src/views/index.ts.
…space ink, uploads, hotkeys

D38-D43 of the genui design diff. New src/overlays.ts exports initOverlays(ctx)
and returns { sync }; src/overlays.css ports the design styles.
…al pills, nav/log pills, hint, chips, ask bar, up-next queue, action log)
Co-locate the views test with the views and re-baseline sentrux for the
new views module.
Pen, ask input and log button use the S1 selectors; S1 owns #attach and the
non-drawing placeholders. Esc runs in capture so it closes one thing.
i098 added 25 commits October 29, 2026 19:16
Cards use their width and fit their text (pretext title sizes, wrap, ellipsis);
short titles with the detail in body; clusters sit apart around the top one;
one-line fixed-height top bar with a collapsible pill row; overview first,
cluster and full-view detail on request, open cards update live; render speed:
fit only changed on-screen cards, no per-frame link work without lines,
static cluster tint, off-screen cards skip paint.
The hidden meter sits off screen, so content-visibility: auto skipped its
content and every card measured 44 px.
Asks were ordered by their status file's mtime, so a busy mate's old asks
filled an open cluster's 8 slots and hid a newer ask from another task.
Each ask now carries the at= stamp of the line that raised it.
The canvas starts empty and draws asks, PRs, crew or usage only when the
owner asks in chat. Each ask shows its real options parsed from the note,
the recommended one tagged, and a free-text answer box. A cluster pages
its asks instead of a bare count, the header counts every ask that waits,
and a model-made decision card is refused.

Cards in a cluster pack in rows with no gaps: each row is as tall as its
tallest card and the cards stretch to fill it (plan rounds 8 and 27).
cards() and diff() grew past the complexity gate with the on-demand topics.
PR cards and the per-card prop patch are now their own functions.
Each ask card shows a model-written line on what it is, one on why it
matters now, and the options as short full labels with the pick marked.
The raw note stays in the full view. Briefs are cached by ask and time.
Asks older than two days are stale and no longer drawn (owner bug 030/031).
…in-board windows, confirm shows sending then sent

Each ask card carries a ref line: the PR it links (or names by number), else the
brief's ref, else the task and its project. A link opens a window card inside
the board; a page that forbids frames (frame-ancestors, X-Frame-Options) shows a
plain blocked note. Only a card's own link opens. On Yes, the confirm card stays
in place, shows Sending, then Sent with the result, then leaves the board.
Selecting or opening a card and asking about it ("more context") now
shows the card's real facts at once inside the same card: an ask's full
note, its task's latest lines, a PR's title and checks. The model's
answer streams in under them. No new card opens. A crew card still takes
a plain instruction as a steer.
The chat model can send one plan line per question: which cards stay, their
order and size, which card stacks behind another, and which pins to the edge.
Every other card goes to a rail under the composer; one tap brings a card back,
and Reset returns the overview. Undoing the question also undoes its plan.
When cards leave, the staying cards move after them, and new cards build last.
genui-window opens, moves, raises, lowers, floats, sinks and closes a
floating card through the agent API. A floating card keeps its spot over
any layout, stacks by layer, comes to the top when grabbed, and a chat
plan never sets it aside.
…e board around it

An ask about a selected card opens its facts in a floating context window
beside it and plans the board around that card: the card stays large in
front, its PRs and asks join it, everything else is set aside. Answer cards
list the real asks, PRs and crew rows in full and wait for the fleet to load
instead of saying it is still loading. Floating windows cap their height and
scroll. genui-window gains --near to open a window over a card.
…rage can move off a slow disk

A plan's kept cards stay in their own project's cluster instead of moving to one shared cluster, and while a plan is on the board all its clusters show at full size. GENUI_STATE moves the board's storage (for example to /dev/shm), so the first snapshot does not wait on fsync when the disk is under heavy write load.
…t wider

[ ] and Tab step through the front cluster's cards in order, h j k l and the arrows pick the nearest card that way,
g G (Home End) jump to the first and last, Enter opens the selected card, and { } now switch clusters. The hint lists
the keys. On a wider screen a card starts 460 px wide (was 380), a small one 320 (was 260), the edge column 320, and
a body shows six lines before it clips (was four).
While Tab is held, Tab, [ ], h j k l, the arrows and g G add the card they reach to the selection instead of
replacing it; a tap already adds. Releasing Tab keeps the selection.
case"Metric":return`<div class="metric"><span class="text muted">${esc(p.label)}</span><br><b>${esc(p.value)}</b><small>${esc(p.unit||"")}</small></div>`;
case"Badge":return`<span class="badge ${p.tone||"info"}">${esc(p.text)}</span>`;
default:return`<div class="text muted">[${esc(e.type)}]</div>`}}
const render=()=>{root.innerHTML=doc.root?node(doc.root):""};
world.append(meter);
}
meter.style.width = `${w}px`;
meter.innerHTML = html;
: "nothing needs you";
const name = spec.projects?.[b.proj]?.name ?? b.proj;
const hd = `<b style="background:${col}">${esc(name)}</b>${esc(say)}`;
if (box.firstElementChild!.innerHTML !== hd) box.firstElementChild!.innerHTML = hd;
let spec: BoardSpec;
const memo = new Map<Element, string>();
/** Writes markup only on change, so a tap that spans a repaint still lands and animations do not restart. */
const put = (el: Element, html: string) => memo.get(el) !== html && (memo.set(el, html), (el.innerHTML = html));
/** Patches the live DOM toward `html`, so kept parts move and change in place instead of re-appearing. */
export function morph(el: Element, html: string) {
const t = document.createElement("div");
t.innerHTML = html;
}, BUILD_MS);
return stamp(el);
}
if (t._html === undefined) el.innerHTML = html;

/** Parses `state [at=epoch]: text`. Returns undefined for any other line. */
export function parseStatusLine(line: string): StatusLine | undefined {
const m = LINE.exec(line.trim());
};

export function createGenuiClient(opts: GenuiClientOptions): GenuiClient {
const base = opts.url.replace(/\/+$/, "");
let doc={root:"",elements:{},style:{},links:[]},seq=0;const t0=performance.now();
const esc=s=>String(s).replace(/[&<>"]/g,c=>({"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;"}[c]));
function apply(ops){for(const o of ops){const k=o.path.split("/").slice(1).map(s=>s.replace(/~1/g,"/").replace(/~0/g,"~"));
if(!k.length){doc=o.value;continue}let p=doc;for(const x of k.slice(0,-1))p=p[x];const l=k.at(-1);o.op==="remove"?delete p[l]:p[l]=o.value}}
let doc={root:"",elements:{},style:{},links:[]},seq=0;const t0=performance.now();
const esc=s=>String(s).replace(/[&<>"]/g,c=>({"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;"}[c]));
function apply(ops){for(const o of ops){const k=o.path.split("/").slice(1).map(s=>s.replace(/~1/g,"/").replace(/~0/g,"~"));
if(!k.length){doc=o.value;continue}let p=doc;for(const x of k.slice(0,-1))p=p[x];const l=k.at(-1);o.op==="remove"?delete p[l]:p[l]=o.value}}

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Todo

Development

Successfully merging this pull request may close these issues.

GenUI fleet board: live fleet canvas, kit and bridge (stack base for #1085 fixes)

2 participants