Library: the AI shelf is stocked without holding the request open, and the AI Atlas is finished - #227
Conversation
Measured on production tonight: a first board on Wolf's library took longer than the edge would hold the request, which closed it at 60 seconds. The board landed anyway, a minute later, unseen by the browser that asked for it, and the owner would have read a gateway error where a shelf was being filled. The roll no longer runs inside the request. The route starts it, records the moment it started, answers "rolling", and returns; the work goes on in the container, which is long-lived, and writes the board when it lands. The shelf asks again every four seconds until the board stands, showing the working state it already had. A roll recorded more than five minutes ago is treated as gone, so a container recreated mid-roll does not leave the shelf waiting for something that is not coming. The note the engine leaves is stored beside the board rather than returned once, so the reason a roll came back short survives the poll that finds it. Probed with the engine stubbed: the first ask answers rolling at once, a second ask does not start a second run, the board lands on the next poll, and the marker is cleared with the note. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
Claude finished @manager's task in 5m 3s —— View job Review: async AI shelf roll (PR #227)
SummaryThe core design is sound: moving the engine call out of the request/response cycle and polling for a Three things worth a look before merge, posted inline:
Nothing else stood out — no App Router/Tailwind/CSS-in-JS violations, no |
| /** When the roll running right now started, UTC. A roll outlives the | ||
| * request that asked for it, so this is what a later request reads to | ||
| * know one is under way. Cleared when it lands. */ | ||
| rollingSince?: string | null; |
There was a problem hiding this comment.
The comment above (and the mirrored one in ai-shelf.ts at the rolling.has(libraryId) || !rollIsStale(stored.rollingSince) check) claims the persisted rollingSince makes the single-roll guarantee "carry across processes." It doesn't, as written: serial()/queue in this file (store.ts:78-86) is an in-process Promise chain, not a file lock. If this route is ever served by more than one Node process (replicas, a restart-then-old-process-still-draining window, etc.), two processes can each readLibrary and see rollingSince: null/stale at the same moment, both pass the check, and both start an engine roll — the exact "two at once... one would be lost" failure this mechanism exists to prevent, just moved from same-process races to a real multi-process race with no lock at all.
If the deployment is guaranteed single-process today, worth a one-line comment saying so explicitly rather than "carries the same fact across processes," which reads as a stronger guarantee than the code provides. If multi-process is possible, this needs an actual cross-process lock (e.g., an atomic "claim" write with a check on the write result) before it's safe.
There was a problem hiding this comment.
Fixed in 7dab594. The comment claimed a guarantee the code does not give. Both places now say what is true: the serial queue in store.ts and the roll set in ai-shelf.ts are one process, the persisted marker is a heartbeat and not a lock, the shelf runs one container per host so a second process cannot start a roll, and serving this route from replicas would need a real cross-process lock.
| state, | ||
| loading, | ||
| rolling, | ||
| rolling: rolling || state?.status === 'rolling', |
There was a problem hiding this comment.
Now that a roll's POST resolves almost immediately with status: 'rolling' instead of blocking for the whole engine run, inFlight/busy are only true for that brief initial request, not for the minute-plus the roll actually takes in the background. This new rolling (returned here) is the flag that now spans the whole roll.
RecommendedShelf.tsx:397 still gates the lock/ban controls on shelf.busy only (not shelf.rolling/working), so an owner can toggle lock or ban a pick while a roll is still in flight. That write reaches the server and updates board.locked/board.picks right away, but stockTheBoard's final updateLibrary in ai-shelf.ts (~line 473) replaces the whole board wholesale using the lockedIds snapshot captured when the roll started — so a lock/unlock applied mid-roll is silently overwritten with no error and no indication to the owner once the new board lands moments later.
Before this PR, busy stayed true for the entire (synchronous) roll, so this race wasn't reachable from the UI. Worth gating those controls on shelf.rolling too (or having the server-side roll merge against the current locked set instead of the one captured at start).
There was a problem hiding this comment.
Fixed in 7dab594. Correct, and reachable: the roll replaces the whole board from the snapshot it took when it started. Preference, lock, ban and unban now wait on working (loading || rolling), not on busy alone, and the re-roll button already did. The reason is written above the flag so the next change does not undo it.
| polls.current = 0; | ||
| return; | ||
| } | ||
| if (polls.current >= POLL_LIMIT) return; |
There was a problem hiding this comment.
Once polls.current >= POLL_LIMIT (90 × 4s ≈ 6 minutes) the effect just stops scheduling further polls — state.status stays 'rolling' forever with no error/timeout surfaced. Combined with the server's 5-minute ROLL_STALE_MS, the common failure mode (container recreated mid-roll) is already handled server-side by then, so this mostly matters for a roll that's genuinely still running past 6 minutes: the owner is left staring at "Stocking the shelf" indefinitely with no manual recovery short of a full page reload (the getAiShelf effect only re-runs on libraryId/enabled changes). Consider surfacing a "still working — refresh to check" state, or resetting polls.current and trying again at a longer interval, instead of silently going quiet.
There was a problem hiding this comment.
Fixed in 7dab594. Out of polls the hook now sets an error the shelf shows: the roll is still working, reload to see where it got to. Silence was the wrong answer.
…a seal Two things, both on the AI surfaces in the dark reading. The writing on a cover was disappearing. `--gradient-cover-placeholder` is declared on :root out of `--cream` and `--beige`, and the dark theme sets those on `body.darkTheme .library`, a different element: the gradient's own var()s were resolved at :root and inherited down frozen at their daylight values. So the cover face stayed paper while `--black-rich` and `--gray-darkest` turned light on the same element, and the title, the author and the magic book's own line were light ink on light paper. Measured off Wolf's screenshot: paper #f2ece2, ink #dbd3c8. The face is right to stay paper: the book mockup under every cover is a photograph and does not turn at night. So the ink stops turning instead. `--ink-on-cover`, `--ink-on-cover-soft`, `--wash-on-cover`, `--accent-on-cover` and `--rule-on-cover` are declared once and never redefined by the theme, and everything that sits on a printed face reads them: both cards' titles and authors, the score slip, the lock and ban buttons, the new-ground mark, the magic book's blank line and its icon. The empty places on the AI shelf were rectangles the colour of the page, which at night made them the page. They are seals now: a bevelled face the size of a cover with a rune cut into it, drawn in the same straight-stroke hand as the library's monograms, two rules where a title will be, and a light behind it that breathes on its own beat. Eight marks, so thirteen places never read as one drawing repeated. While the engine works the row lights in order, a beat apart, so the wait reads as a spell running down the shelf. Reduced motion holds all of it still. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
Second commit on this branch, same release: the dark reading of the AI surfaces. The ink bug. The empty places. They were rectangles the colour of the page, which at night made them the page. They are seals now, cut in the same straight-stroke hand as the library monograms, with a light that breathes and, while the engine works, runs down the row a beat at a time. |
|
Claude finished @manager's task in 2m 44s —— View job Review: async AI shelf roll (PR #227, sync)
What's new since the last passThe new commit (
Still open from the previous review roundThis commit doesn't address the three points raised earlier today, and they remain live in the current
None of these are regressions introduced by today's commit — they're carried over from No new App Router/Tailwind/CSS-in-JS violations, no |
The road to the engine, not the engine, sets the ceiling: between a project container and the relay stands an edge that closes a request at 100 seconds, and two opus calls measured from production at high effort took 107 and 117. Latency here is mostly what the model writes, so each half now asks for twelve and five candidates instead of fourteen and six, which is what the board needs plus a margin the second pass covers. The picks themselves are untouched: opus 5, high effort, same rubric. A call the edge cuts mid-answer is now told apart from a call that answered nothing: the shelf says the engine ran long and the line closed, the journal records it, and nothing is retried automatically, because the model kept writing on the other side and the subscription has already paid once. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
Claude finished @manager's task in 2m 50s —— View job Review: async AI shelf roll (PR #227, third commit)
What's new since the last passThe new commit (
No new App Router/Tailwind/CSS-in-JS/ Still open from earlier roundsUnrelated to today's commit, unaddressed, and previously flagged inline:
Per the release-lessons note in Did not run |
| if (error instanceof RelayError) { | ||
| if (error.exhausted || error.status === 0) | ||
| half.tracksExhausted = true; | ||
| if (CUT_OFF.has(error.status)) half.cutOff = true; |
There was a problem hiding this comment.
CUT_OFF (line 379: 408, 502, 504, 522, 524) only matches when the intermediate edge answers with one of those HTTP statuses through askRelay's !r.ok branch. If that edge instead drops the TCP connection outright when it gives up at 100s (rather than sending a proper gateway-timeout response), fetch throws before a Response exists, and relay.ts:77-83 maps any such failure to RelayError(..., 0) — status 0, which isn't in CUT_OFF. That path only sets tracksExhausted (via error.status === 0), so the owner would see "Nothing could be confirmed this time" instead of the new "The engine ran long and the line closed" note, even though the root cause is exactly the cut-off case this commit is meant to surface.
Worth confirming against the actual production timeout behavior (does the edge return a real HTTP error, or reset the connection?) before relying on this distinction for the note shown to the owner — the PR body's "probed on the dev tree with the engine stubbed" wouldn't have exercised the real edge's failure mode either way.
There was a problem hiding this comment.
No change, and here is why. With the handover in place the long wait is a series of short polls, so the 100s edge no longer sits across a running call; the direct path is the only one that can still be cut mid-write. Which of the two an edge does under that path (a gateway status, or a reset) I have not measured on production, and the note the owner reads is the same class of failure either way: nothing came back. I am not guessing at a distinction I cannot evidence. If you want it split, the evidence has to come from a real long roll on prod first.
The Order built the relay's job mode tonight on the word I gave: the same Messages body posted to the job endpoint comes back accepted at once, and the answer is collected by polling. That removes the ceiling the edge put on this: a proxied request dies at 100 seconds, opus 5 at high effort on a library this size runs past it, and until now the roll was racing a clock it could not see. Every hop is short now and the model takes as long as it takes. A poll that fails on the way is not the job failing: the work is running and the subscription has already paid for it, so the next poll asks again until the deadline, and only a job the relay has forgotten or a run past five and a half minutes is given up on. A relay that does not know the job endpoint answers 404 and the call falls back to the old held-open request, so a container wired before tonight keeps working. Probed both paths on the dev tree against a relay stub: two halves handed over in the same second, polled while running, the board of thirteen landing on the poll after; and with the job endpoint answering 404, the same roll going through on the direct path. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
Claude finished @manager's task in 2m 43s —— View job Review: async AI shelf roll (PR #227, sync on new commit)
What's new since the last passThe new commit (
One real issue found in this commit, posted inline on
This also changes the shape of last round's "status 0 vs Still open from earlier roundsUnrelated to today's commit, unaddressed, previously flagged inline:
No new App Router/Tailwind/CSS-in-JS/ Did not run Per the release-lessons note in |
| /** How often the answer is asked for. */ | ||
| const POLL_MS = 3_000; | ||
| /** Past this the job is abandoned; the relay never runs one this long. */ | ||
| const JOB_DEADLINE_MS = 330_000; |
There was a problem hiding this comment.
JOB_DEADLINE_MS (330s) legitimately lets a single askRelay call run for up to 5.5 minutes now that it's polling a job instead of being cut off by the 100s edge. But runHalf in engine.ts:447 makes up to two sequential askRelay calls per half (fit/stretch) when the first pass doesn't fill the need, and ROLL_STALE_MS in src/pages/api/library/ai-shelf.ts:60 is fixed at 5 minutes with no renewal of rollingSince while the roll is in progress (ai-shelf.ts:374, only cleared at the end).
Before this commit, a call was effectively bounded by the 100s edge failing fast, so a full roll rarely approached the 5-minute staleness window. Now that a single call can legitimately take up to 330s — and a half can make two of them — a real roll (e.g. first pass ~110s + verification + a needed second pass) can easily cross the 5-minute mark while askAsJob is still correctly polling. When that happens, rollIsStale(stored.rollingSince) in ai-shelf.ts:363 returns true for a roll that is still legitimately running, and a second concurrent roll can be kicked off for the same library — the exact "two at once, one is lost" scenario rollingSince exists to prevent.
Worth either raising ROLL_STALE_MS comfortably above JOB_DEADLINE_MS (plus verification time), or renewing rollingSince on each poll/pass so staleness only fires when a roll is actually abandoned (e.g., container died), not just running long.
There was a problem hiding this comment.
Fixed in 7dab594, and this was the real bug in the batch. A running roll now renews rollingSince every 60s (ROLL_BEAT_MS) and the interval is cleared in the finally. ROLL_STALE_MS keeps its five minutes but now means "no heartbeat for five minutes", so a roll that legitimately runs long is never declared stale under itself and rolled a second time.
The Order's correction, and it is right: a 404 on handing the job over means this relay does not know jobs at all, which is where the old held-open call belongs, but a 404 while polling means something else entirely. The job is gone, from a restart or from its hour of keeping, and whatever was running behind it died with the relay that forgot it. Holding a call open there would make the subscription pay twice for one answer. So a lost job is dealt again on the same path, once, and only while enough of the deadline is left to be worth it. Past that the roll says the relay lost the job rather than quietly starting a third. Probed with a stub that forgets both halves on their second poll: each was handed over again and the board of thirteen landed, with four jobs in the relay's log and no direct call anywhere. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
Claude finished @manager's task in 1m 41s —— View job Review: async AI shelf roll (PR #227, sync on new commit)
What's new since the last passThe new commit ( One thing worth a look, posted inline on
Still open from earlier rounds (unaddressed, unrelated to today's commit)
No App Router/Tailwind/CSS-in-JS/ Did not run Per the release-lessons note in |
| const handed = await handOver(url, request); | ||
| if (!handed) return askDirect(request); | ||
| const answer = await collect(handed.poll, request, deadline); | ||
| if (answer !== 'lost') return answer; |
There was a problem hiding this comment.
The resubmit-on-lost-job logic assumes a poll 404 (collect's asked.status === 404 → 'lost', line 248) always means the underlying run died with the relay, so resubmitting "costs nothing" (per the comment on line 227-228 and this function's doc comment). That's true if claude-relay is a single instance holding job state in memory (which the file header at line 7-8 implies: "The Order's container", singular). But if it's ever scaled to more than one replica behind a load balancer, a poll routed to a different instance than the one that accepted the job would also 404 even though the original job is still running — and askAsJob would then hand the same request over a second time via handOver, running (and paying for) the model call twice, contradicting the "nothing is paid twice" comment.
Not necessarily a bug today if the relay is guaranteed single-instance, but worth a one-line note confirming that assumption (similar to the rollingSince "across processes" comment flagged earlier in this PR on store.ts), since nothing here enforces it.
There was a problem hiding this comment.
Fixed in 7dab594. The assumption is now written into the doc comment on askAsJob: job state lives in the single relay container, so a 404 is a job nobody is running and dealing it again pays for nothing twice. It also says what breaks behind replicas and what would have to change.
The Atlas left here at revision 93c5744 and ran on the Terminal for a month. Wolf asked for what it learned there to come back to keepsimple.io/ai-atlas. This takes the view, not the content: the map still describes KeepSimple, in English and Russian, on our own data and our own metrics. What came back, all of it worked out on this same composition: - The Security passage runs on one clock. The dot's fall and every ring's light are painted from one solved trajectory, so a ring brightens exactly as the dot crosses it and goes dark before the next fall. It was two guesses before, an SMIL animation and separate CSS timing that never met. Reduced motion leaves the dot hidden and the rings dark. - Callouts end inside the frame. Each scrolls in place, and the last two rows are held shorter still; stacked under 1100px they get their height back. - The dossier heading sits inside the scrolling panel and wraps, so a long title no longer needs a measured padding under it and no longer covers what it names. - Every node and every dossier row that leads somewhere takes focus and answers Enter, and the focus ring is the Atlas red. - A topics control in the rail opens any dossier without hunting for its node. - Switching Environment and Security crossfades instead of cutting. - The type floor is raised: prose at 14, labels at 12, nothing under it. What did not come back: the Terminal's own content, its clockwise task route and stage supports, which describe a task moving through the harness and mean nothing on this map, and its doctrine tile, which replaced our illustration. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
Wolf asked tonight for the Terminal Atlas view to replace what stands at keepsimple.io/ai-atlas, so the port joins this batch. Terminal's Atlas is this page's own composition, forked at 93c5744 and worked on there for a month. Their Taken: the Security passage on one solved trajectory (the dot's fall and every ring's light painted from the same clock, dark again before the next fall, nothing at all under reduced motion); callouts that end inside the frame, with the last two rows held shorter and their height returned when stacked under 1100px; the dossier heading inside the scrolling panel, wrapping, with no measured padding under it; focus and Enter on every node and every dossier row that leads somewhere; a topics control in the rail; a crossfade between Environment and Security; and the raised type floor, prose at 14 and labels at 12. Left behind: their content, their clockwise task route and stage supports, which describe a task moving through the harness and mean nothing on this map, and their doctrine tile, which replaced our illustration. |
|
Claude finished @manager's task in 4m 49s —— View job Review: async AI shelf roll (PR #227, sync on new commit)
What's new since the last passThe new commit (
One real issue found, posted inline on
No App Router/Tailwind/CSS-in-JS violations, no new Still open from earlier rounds (Library AI shelf, unrelated to today's commit)
Did not run |
| .ai-atlas-root .security-canvas .ring__circle { | ||
| stroke: color-mix( | ||
| in srgb, | ||
| var(--red) calc(var(--security-passage, 0) * 100%), | ||
| var(--rule) | ||
| ); | ||
| stroke-width: calc(1px + var(--security-passage, 0) * 1px); | ||
| } |
There was a problem hiding this comment.
This new rule drives stroke/stroke-width from --security-passage, which SecurityRings' requestAnimationFrame loop (src/pages/ai-atlas.tsx:1349-1379) updates on every frame (~16ms). But .ring__circle still carries a pre-existing transition: stroke 0.18s ease, stroke-width 0.18s ease, opacity 0.18s ease; (src/styles/ai-atlas.css:560-565, unchanged by this commit — it was written for the old discrete hover toggle). Transitioning a property whose target value changes every ~16ms means the browser is perpetually easing toward a moving target with a 180ms time constant: the rendered stroke will visibly lag the dot's true position by roughly that amount rather than lighting "exactly when the dot crosses it," which is the precision this commit's own comment (and securityPassage.ts's header) claims.
Suggest scoping the stroke/stroke-width transition off for the passage-driven state, e.g. transition: opacity 0.18s ease; on the base rule with stroke/stroke-width transitions only reapplied via .ring__circle.is-glow/.is-dim (the discrete hover states that still want the 0.18s ease), so the continuous per-frame animation isn't fighting a CSS transition.
Related: since this base rule now unconditionally sets stroke/stroke-width via CSS, the JSX presentation attributes on <circle> at src/pages/ai-atlas.tsx:1404-1405 (stroke={isHovered ? 'var(--red)' : 'var(--rule)'}, strokeWidth={isHovered ? 1.6 : 1}) are now dead — CSS class rules always win over presentation attributes, so neither branch of either conditional is ever visually reachable. Worth removing them (or the CSS override) so the code doesn't suggest hover changes width when only .is-glow's color override actually applies.
There was a problem hiding this comment.
Fixed in 7dab594. The security rings now carry transition: opacity .18s only, so the passage drives stroke and stroke-width straight from the animation frame with nothing easing against it.
Wolf's word, twice: replace, not merge. The previous page, its content and its locale strings are gone. What stands at /ai-atlas is the Atlas as the Terminal serves it at revision 64: their view, their adapter, their copy and their guide, with the composition they took from here in the first place. - `src/pages/ai-atlas.tsx` is their `atlas-source/atlas.tsx`, with the frame's `createRoot` bootstrap dropped, the guide read from `/ai-atlas/guide.json` on this host, and our SEO head kept over it. - `src/lib/aiAtlas/` carries their adapter and copy beside the security passage. The adapter turns the guide into six stages, Project through Result, and seventy-four dossiers. - `src/styles/ai-atlas.css` is their `original.css` followed by their integration rules, with the asset URLs pointed at `/ai-atlas` and every global rule held to `body.ai-atlas-page`: the Atlas owns this page, not the document, because it shares a site here. - `public/ai-atlas/` gains the guide, the paper texture and the seven local font files their sheet asks for. Two consequences worth stating plainly. The page is English only, as the Terminal's is; `/ru/ai-atlas` now renders English. And the content is the Terminal's own documentation, which until tonight sat behind Access. Probed on the dev tree: page, guide, fonts, texture all 200, and the adapter run against the shipped guide gives six stages and seventy-four dossiers. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
Claude finished @manager's task in 4m 55s —— View job Review: async AI shelf roll (PR #227, sync on new commit)
What's new since the last passThe new commit ( The main finding, posted inline on Two smaller items posted inline:
Two things confirmed as intentional, not bugs — no comment needed: the commit message explicitly states No App Router/Tailwind/CSS-in-JS violations. Font sizes introduced in this commit (12/14/16/18/22/40px) are all within the CLAUDE.md font passport. Hardcoded hex colors in Still open from earlier rounds (unrelated to today's commit)Library AI-shelf, unaddressed:
Atlas, carried over unchanged by this commit (same ported CSS/JSX, not re-flagged with new inline comments to avoid duplicating existing threads):
Did not run Per the release-lessons note in |
| "basis": "The ownership charter, delegated exceptions included." | ||
| }, | ||
| { | ||
| "id": "secrets", |
There was a problem hiding this comment.
Worth flagging explicitly, even though the commit message states this is Wolf's call twice-over.
This file — and the /ai-atlas route that renders it — makes the Terminal's own internal operations documentation public with no auth gate (middleware.ts's matcher excludes api/_next/keepsimple_/favicon.ico only; /ai-atlas isn't carved out). The commit message says this content "until tonight sat behind Access" (Cloudflare Access). Concretely, guide.json is a plain static asset under public/, fetchable by anyone at /ai-atlas/guide.json regardless of anything the React UI does with it.
The content itself is architecture-level operational documentation of the server this very site runs on: the credential-boundary policy (secrets node, this line), the SSH "server cockpit" gate, the Restic backup schedule (recovery), the Telegram alert/voice-task relay, and — pointedly — the access node's own description explains that Cloudflare Access "checks who you are before an internal app" while "public sites answer anyone," which is exactly the boundary this content just crossed. No literal secrets/tokens/IPs are present (checked), but this is real reconnaissance-grade information disclosure about internal security architecture for anyone who finds the page.
Two things worth confirming before this ships to production, independent of whether Wolf already approved the disclosure in principle:
- Is publishing the specific architectural detail here (not just "an Atlas exists") the intended scope, or should the guide be trimmed before it's public?
- See the separate comment on
ai-atlas.tsx— the ported UI has anode--redactedrendering path implying the source system can hide certain nodes, but nothing in this exportedguide.jsonuses it. Worth confirming that's because there's genuinely nothing more sensitive to hide in this export, not because the redaction flag was dropped when the guide was captured for this public host.
There was a problem hiding this comment.
By design, and it is the owner's call, made repeatedly and in writing. What ships is not an operations document any more: the Terminal's own cross-links and system edges are gone from the bundle in this batch (34 sentences), and the prose on the cards is written by the owner for this page, naming features and rules, no hosts, no paths, no credentials, no inventory. The commit before this one added a commit-time guard for exactly that class of leak.
| guide.system.nodes.includes(entry) | ||
| ? 'system-' + entry.id | ||
| : entry.id; | ||
| dossiers[id] = { |
There was a problem hiding this comment.
adaptGuide never sets a .redacted field on any dossier/project/system-node it produces, and public/ai-atlas/guide.json has zero occurrences of a "redacted" key. But src/pages/ai-atlas.tsx's NodeBody (pre-existing, ~line 220) has a whole rendering path for node.redacted — a placeholder/blur treatment plus dedicated copy (t.redactedPlaceholder) — implying the source system can mark certain nodes too sensitive to show even to itself. That path is dead on the data this adapter produces.
Given the guide already documents the credential-boundary policy, the SSH "server cockpit" gate, and backup/alerting internals (see the comment on guide.json), worth confirming explicitly that this export genuinely has nothing that needs redacting — rather than a redaction flag having been silently dropped when the guide was captured for this now-public host.
There was a problem hiding this comment.
Correct, and intentional: adaptGuide sets no redacted field and no redacted content ships. The rendering path in NodeBody comes with the component and stays with it; it costs nothing and it is what a future source would use if it ever marked a card.
| export const copy: any = { | ||
| ...base, | ||
| linesValue: (n: number) => String(n), | ||
| introInhabitantsTpl: () => '', |
There was a problem hiding this comment.
This new file types copy, guide, dossiers, and every helper param/return as any throughout (AGENTS.md: "Avoid any, prefer precise types"). I'm not flagging this as blocking since ai-atlas.tsx already carries ~48-60 pre-existing anys from the prior port and this is clearly transplanted verbatim from the Terminal repo rather than hand-written here, but since this is a brand-new file (not a diff against prior KeepSimple code), it's worth at least a shared Guide/Dossier interface at the boundary (adaptGuide(guide: Guide): AtlasData) so a future change to guide.json's shape fails at compile time instead of silently producing undefined deep in the render tree.
There was a problem hiding this comment.
Acknowledged, not done in this batch. The file is the seam to a foreign shape and typing it properly means typing the guide it reads; that is its own change with its own review, not a rider on a content release.
| @@ -0,0 +1,49 @@ | |||
| @font-face { | |||
There was a problem hiding this comment.
All seven faces here are raw TTF (font-0.ttf … font-6.ttf, 112KB–392KB each, ~1.7MB total). AGENTS.md's "Web fonts" section requires the site's fonts to ship as WOFF2 with TTF kept only as build sources (yarn fonts:build / scripts/fonts/build-webfonts.cjs, public/fonts/<Family>/) — this directory bypasses that pipeline entirely and serves the uncompressed originals straight to the browser. WOFF2 is typically 30-50% smaller than TTF for the same glyph set, so this is roughly half a megabyte or more of avoidable transfer on every first visit to /ai-atlas. Worth running these through the existing font-build tooling (or at minimum adding .woff2 versions) rather than shipping TTF directly, even though this whole directory was ported wholesale from the Terminal's own asset set.
There was a problem hiding this comment.
Fixed in 7dab594. All seven faces are WOFF2 now, 1.72 MB down to 0.60 MB, the TTF sources are gone from the tree (nothing server-side reads them, unlike the Library thumbnail fonts) and the stylesheet version is bumped so no visitor keeps the old sheet.
…n click The Terminal's guide described each mechanism from the inside, for a reader who already lives in Terminal. The page is for a reader who does not, so the prose on every card is now Wolf explaining the feature: what it lets him do and why it exists. The guide keeps the ids, stages and links; the prose lives in src/lib/aiAtlas/features.ts and survives a guide refresh. A click now opens the card in a modal over the sheet, hover keeps the rail. Esc, the scrim or the stamp closes it; topics inside open in place with a crossfade. Enter and exit are eased and honor reduced motion. The card's paragraphs render as paragraphs in both the rail and the modal. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The Terminal's export of the guide brought its source references (file, line, sha256 of the Terminal's code), its placement notes and a tool inventory onto a public path. The page renders none of it. A script strips the guide to ids, titles, prose, children, links and system nodes, and runs after every refresh. 46 KB to 25 KB, same six stages and seventy-four dossiers out of the adapter. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The guide describes the private Terminal. It now lives beside the adapter, is imported into the page and rendered on the server, and no longer sits under public/ where any request could pull it as JSON. /ai-atlas/guide.json answers 404. The strip script follows the file. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
edacf99 to
dfad950
Compare
|
Claude finished @manager's task in 3m 7s —— View job Review: async AI shelf roll (PR #227, sync on new commits)
What's new since the last passAll four new commits are Atlas-only; none touch the Library AI-shelf files reviewed earlier. The public-disclosure concern from the last round is resolved. One real issue found in One smaller item, same file: No App Router/Tailwind/CSS-in-JS/ Still open from earlier rounds (unrelated to today's commits)Library AI-shelf, unaddressed:
Atlas, carried over unchanged by today's commits:
Did not run Per the release-lessons note in |
| > | ||
| <div className="feature-modal__scrim" onClick={onClose} /> | ||
| <div | ||
| className="feature-modal__card" |
There was a problem hiding this comment.
Minor a11y gap: the modal card has role="dialog" / aria-modal="true" and closes on Escape (line ~635-641), but there's no Tab-key focus trap — a keyboard user can Tab past the close button/last row and land on elements behind the scrim while the modal is still visually open. Worth trapping Tab/Shift+Tab within cardRef for a dialog with this contract (not a regression, since the page had no modals before this commit, but worth tightening given the new pattern).
There was a problem hiding this comment.
Fixed in 7dab594. Tab and Shift+Tab are trapped inside the card, focus lands back on the first stop from outside, Escape still closes.
This repository is public. The guard reads the added lines of the staged diff and refuses source fingerprints (sha256, file + line), internal paths and unstripped exports under public/. Existing lines are not judged. No bypass; the offending line is rewritten. One journal line per run. The charter records the rule and the incident that produced it. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
| </aside> | ||
| </div> | ||
|
|
||
| <FeatureModal |
There was a problem hiding this comment.
When this opens (click on a node in Environment view, focusedNode set), the rail <Dossier> panel in the <aside> above (fed by dossier, derived at line ~1391 from focusId = hoverNode || focusedNode) shows the same card behind the modal's scrim, since focusId still resolves to focusedNode when nothing is hovered.
The commit message states the intended split plainly: "hover keeps the rail" (click gets the modal instead). As written, a click gets both — identical title/prose/rows rendered twice, once in the modal and once in the aside. Consider deriving the rail's dossier from hoverNode only while viewMode === 'environment' (or clearing/hiding the rail card while this modal is open for the same id).
There was a problem hiding this comment.
Fixed in 7dab594. The rail now falls back to the intro while the modal holds a card, and takes the hovered card the moment the pointer picks one. The split the commit message described is the behaviour now.
|
Claude finished @manager's task in 3m 20s —— View job Review: async AI shelf roll (PR #227, sync on new commits)
What's new since the last passThree commits, all responding to the "guide.json publishes internal Terminal documentation" finding from the prior round:
The main finding, posted inline on Related: No other issues found in these three commits — the lint-staged wiring, Still open from earlier rounds (unrelated to today's commits)Library AI-shelf, unaddressed (no library files changed in this sync):
Atlas, carried over unchanged by today's commits:
Did not run Per the release-lessons note in |
| pick(s, ['id', 'title', 'location', 'text', 'children']), | ||
| ), | ||
| entries: guide.entries.map(e => | ||
| pick(e, ['id', 'title', 'text', 'detail', 'children']), |
There was a problem hiding this comment.
This strip only removes the Terminal's metadata — where/when/note/category/sourceLabel/source (file, line, sha256) — from entries, and additionally keeps basis on system.nodes (line 31). It does not touch detail/text, which is where the actually sensitive content lives.
Concretely, src/lib/aiAtlas/guide.json still ships (and is now bundled straight into /ai-atlas, plus visible in this public repo's source either way):
secretsnode:"The Order keeps the shared credentials. A project operator gets the ones its grant names and none of the others."(guide.json:624)server cockpit:"...carries the operations I allow over SSH. Changes to shared infrastructure go through The Order."(guide.json:633)recovery:"Restic copies selected state off the server every night, encrypted. Coverage has exclusions..."(guide.json:714, 732)
This is exactly the class of content the earlier review round flagged as the substantive disclosure (credential-boundary policy, SSH gate, backup schedule, alert relay) — moving the file from public/ to src/lib/aiAtlas/ and stripping source fingerprints doesn't remove it, it's still rendered on the public page and still sitting in this OSS repo's source tree regardless of any runtime endpoint. The commit title ("a commit that adds private infrastructure data is refused") and the new CLAUDE.md section read as if this closes the incident, but the highest-sensitivity content from the original finding is still live.
scripts/guard/no-internal-data.mjs won't catch a future recurrence of this either — its rules match sha256, file+line, specific internal-path substrings, and (only under public/) the where/when/basis/sourceLabel keys. None of those patterns match free-text prose like "carries the operations I allow over SSH", so a similar export landing outside public/ (as this one now does) would sail through the guard.
If the intent is really "nothing describing private infrastructure goes into the tree" (per the new CLAUDE.md rule), this script needs to drop or redact detail/text/basis for sensitive nodes/entries too — not just their sourcing metadata. [Fix this →](https://claude.ai/code?q=In%20scripts%2Fai-atlas%2Fstrip-guide.mjs%20on%20branch%20fix%2Flibrary-ai-shelf-async-roll%2C%20the%20strip%20only%20removes%20source%20metadata%20(where%2Fwhen%2Fnote%2Fcategory%2FsourceLabel%2Ffile%2Fline%2Fsha256)%20from%20the%20Terminal%27s%20guide%20export%2C%20but%20keeps%20%60detail%60%2F%60text%60%2F%60basis%60%20prose%20verbatim.%20That%20prose%20still%20describes%20sensitive%20internal%20infrastructure%20(credential%20boundary%20policy%20on%20the%20%27secrets%27%20node%2C%20SSH%20%27server%20cockpit%27%20access%2C%20Restic%20backup%20schedule%2C%20Telegram%20relay)%20in%20src%2Flib%2FaiAtlas%2Fguide.json%2C%20which%20is%20now%20bundled%20into%20the%20public%20%2Fai-atlas%20page%20and%20also%20sits%20in%20this%20public%20repo%27s%20source.%20Add%20redaction%2Fremoval%20of%20detail%2Ftext%2Fbasis%20for%20sensitive%20nodes%20(secrets%2C%20ssh%2Fcockpit%2Frecovery%2Ftelegram)%2C%20or%20confirm%20with%20Wolf%20that%20this%20specific%20content%20is%20intentionally%20public%20before%20relying%20on%20this%20script%20as%20the%20fix%20for%20the%20earlier%20disclosure%20finding.
There was a problem hiding this comment.
Fixed in 7dab594 and in the commit before it. The strip now also drops links and system edges, which is where the Terminal's own explanatory sentences lived. detail and text stay on purpose: that is the prose the page draws, written by the owner for a public page, and it is his call.
Every entry now carries the name printed on its card, and the twelve black tiles on the outer ring are marked TILE so the one being edited is found by its label rather than by an id. A header states the rules of the file: a string is a paragraph, a key is never renamed. check-features.mjs reports a key that matches no card, which would be words that never show. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
|
Claude finished @manager's task in 3m 18s —— View job Review: async AI shelf roll (PR #227, sync on new commit)
What's new since the last passThe new commit (
No App Router/Tailwind/CSS-in-JS/ Still open from earlier rounds (unrelated to today's commit)Library AI-shelf, unaddressed:
Atlas, carried over unchanged by today's commit:
Did not run Per the release-lessons note in |
| const ids = new Set([ | ||
| ...guide.steps.map(s => 'stage-' + s.id), | ||
| ...guide.entries.map(e => e.id), | ||
| ...guide.system.nodes.map(n => n.id), | ||
| ...guide.system.nodes.map(n => 'system-' + n.id), |
There was a problem hiding this comment.
The valid-ids set is more permissive than the ids adapter.ts actually produces, which creates a blind spot in exactly the check this script exists to run.
adapter.ts:16-21 only prefixes a system node's id with system- when that id also exists among guide.entries (a real collision — today just tools, since terminal appears in guide.entries... actually only as a step, which doesn't collide, so system terminal keeps its plain id). But here, every system node contributes both n.id and 'system-' + n.id to the valid set unconditionally (lines 24-25), regardless of whether it actually collides.
Concretely: guide.system.nodes includes wolf, access, terminal, secrets, apex, etc. — none of which collide with an entry — so this script would treat a typo'd key like 'system-wolf' or 'system-terminal' in features.ts as valid, even though adaptGuide would never produce that dossier id and the words behind it would silently never render. That's precisely the "dead key" failure mode this tool is meant to catch.
No key in features.ts hits this today (only the genuinely-colliding 'system-tools' is used), so it's latent rather than active, but worth tightening since Wolf edits this file by hand per the header comment and the check's whole job is to catch exactly this class of mistake:
const entryIds = new Set(guide.entries.map(e => e.id));
const ids = new Set([
...guide.steps.map(s => 'stage-' + s.id),
...guide.entries.map(e => e.id),
...guide.system.nodes.map(n =>
entryIds.has(n.id) ? 'system-' + n.id : n.id,
),
]);There was a problem hiding this comment.
Fixed in 7dab594. The script now mirrors adapter.ts exactly: a system node keeps its own id unless that id also names an entry, and only then is it prefixed. The blind spot is closed and the count it prints dropped from 89 to 71, which is the number of cards the page can actually reach.
…ows them The tiles now carry the features he actually wants a reader to see, and the prose on each is his: why the thing exists, what it does for him, what broke before it existed. Map changes that follow from that: Composites is Composite Keys, Attachments gives its tile to Smart Queuing, Tracks folds into Engine switch and Live Steering takes the free slot, Session resume becomes Session start, Local CLAUDE.md joins Prepare as a third tile, and Doors leads the Work stage. A stage carrying three tiles gets a wider arc so they do not overlap. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
|
Claude finished @manager's task in 3m 41s —— View job Review: async AI shelf roll (PR #227, sync on new commit)
What's new since the last passThe new commit ( Correctness check (adapter.ts/guide.json wiring): verified by hand — every id in the new Two issues found, posted inline on
No App Router/Tailwind/CSS-in-JS/import-order issues — this commit touches no components or styles. No UI/styling changes touch the Library AI-shelf surfaces reviewed earlier. Still open from earlier rounds (unrelated to today's commit)Library AI-shelf, unaddressed:
Atlas, carried over unchanged by today's commit:
Did not run Per the release-lessons note in |
| ], | ||
| // TILE: Doors | ||
| 'work-checks': [ | ||
| 'I got tired of agents agreeing with a rule and then breaking it an hour later. Reading a rule and following it turned out to be two different things, so I stopped writing rules and started running them. Doors are small programs that sit at fixed moments of a turn. When one fires, the agent goes back and fixes the work. There is no way to skip it and no flag to turn it off.', |
There was a problem hiding this comment.
This is a substantial escalation of the disclosure concern raised repeatedly in earlier rounds on guide.json (never fully closed — strip-guide.mjs only strips source metadata, not detail/text). This commit adds much more specific first-person prose describing this project's own internal tooling directly into features.ts, which flows into the dossier text bundled onto the public /ai-atlas page via describe()/adaptGuide:
- Line 92: exact subscription counts/scale ("three to five tracks... around five Claude subscriptions and five Codex subscriptions standing ready") — business/cost-scale detail about the operator's infrastructure.
- Lines 228-232 (
work-checks/"Doors"): a fairly precise description of this repo's own pre/post-edit review gates — including the specific checks run after every edit ("Type sizes and colours get checked against the project's passport... Animations get checked for reduced motion... text gets read for AI filler") — which reads as a description of the very review pipeline this PR is going through, now public. - The
local/global/session-resume/keysentries similarly add detail on the nightly transcript-audit/notice mechanism, the memory-hook system, and the internal "Composite Keys" catalog mechanics.
None of scripts/guard/no-internal-data.mjs's rules (sha256/file+line/specific paths/where+when+basis+sourceLabel under public/) would catch any of this — it's exactly the free-text-prose gap the guard was already flagged as having. Worth a deliberate confirmation that this level of detail about the operator's own tooling is intended to be public, rather than assuming the earlier "guide is fixed" commits (62438d0/dfad950) already covered it — this commit adds new sensitive-adjacent prose after that point.
There was a problem hiding this comment.
Same answer as on guide.json, and the same author. This prose is written by the owner about his own system for a public page: features, rules and how he works, with no host, no path, no credential and no tool inventory. The line about apartments that came close was cut before this push, and a commit-time guard refuses the class of data you are pointing at.
| Involve an agent, | ||
| Conversation history, Saved decisions. | ||
| card: every other card, opened from a stage, a ring or the Topics list. | ||
|
|
There was a problem hiding this comment.
Minor doc drift introduced by this commit: the header says "Twelve of them" and lists 13 names, but omits "Doors" — which this same commit turns into a real TILE (adapter.ts's chosen[4] now starts with 'work-checks', and this file tags it // TILE: Doors at line 226). Actual current TILE count is 14 (keys, backlog, message, queue, engine-switch, steering, global, local, session-resume, work-checks, sendto, skills, history, decisions). Relatedly, the engine-switch entry's own comment (line 90) is still tagged // card: Engine switch even though it's also a TILE now per chosen[2].
Since scripts/ai-atlas/check-features.mjs only checks key validity against the guide, not this header list or the TILE/card tags, nothing currently catches this drift — worth updating the count/list and the engine-switch tag so a future hand-edit doesn't trust a stale map of which entries are on-map tiles.
There was a problem hiding this comment.
Fixed. The header now reads fifteen and lists all fifteen, Doors and Discipline included.
Involve an agent becomes AI Collaboration and says the ownership rule first. Human Collaboration is new: the roster carries people with handles, a line addressed to one leaves for Telegram, and a granted person can hand a task back and receive the result in the same chat. Skills leaves the tile row and stays a card; it is a commodity now and the tile was spent on it. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
|
Claude finished @manager's task in 2m 22s —— View job Review: async AI shelf roll (PR #227, sync on new commit)
What's new since the last passThe new commit ( Verified One issue found, posted inline on No App Router/Tailwind/CSS-in-JS/import-order issues — this commit touches no components or styles. Still open from earlier rounds (unrelated to today's commit)Library AI-shelf, unaddressed:
Atlas, carried over unchanged by today's commit:
Did not run Per the release-lessons note in |
| 'MemPalace is the store where decisions survive. An agent files what we agreed, and a new session searches it before asking me the same question again.', | ||
| 'It is organized by project and searchable by every agent.', | ||
| ], | ||
| // TILE: Skills |
There was a problem hiding this comment.
This commit's adapter.ts:57 change drops skills from chosen[4] (replaced by human-collab), so skills is no longer rendered as an outer-ring tile — it's demoted to a regular card, still reachable via the agent stage's rail but not "filled" anymore. This inline tag is still // TILE: Skills, which is now stale and should read // card: Skills.
This is the same class of drift already flagged in the previous review round on engine-switch (tagged // card: while it's actually a tile per chosen[2]) — now there are two tags pointing the wrong way, in opposite directions. Neither the file's own header list (line 10, "Twelve of them", already undercounting the real 14 tiles) nor scripts/ai-atlas/check-features.mjs (which only checks key-vs-guide existence) catches either mismatch.
There was a problem hiding this comment.
Fixed in 7dab594. It reads // card: Skills now.
…the page has a night The map now says what it is. The header reads WOLF'S TERMINAL and the banner under it names the page. Ring II is Resources, not Context: it holds what every stage draws on. Ring III finally prints its own name; it was filtered out of the render. Project agents becomes Colleagues and carries both diamonds the legend names, people and agents. Result gains Discipline as its third tile: a result sent back is read overnight and comes back as a rule. Conversation history says why the record is kept, the monthly review that proposes what to change. The six stage cards were written before the tiles existed and are rewritten to match them. The four resource cards carry real content: the memory layers and what is mined from them, the tools including the network, the engines and where local models actually stand, the roster of colleagues. The Order is described as the orchestrator it is. The Terminal's own cross-links are gone from the guide and from the bundle: 34 sentences in another voice under cards written in Wolf's. Rows under a card are its own contents, labelled by what they are. Hover now works on every ring. One relation graph drives it: Wolf to The Order, The Order to each resource, each resource to the stages and tiles that use it, and the lines are drawn where they were missing. Stage-to-mechanism wires are legible before a stage is picked. Dark mode arrives on the site's own switch, no second theme: the palette flips on the variables the sheet already draws with, and every hardcoded paper colour gets its night value, modal, Security, dropdown and scrollbars included. Text holds 4.5:1 or better throughout. The rail card is capped at the height of the map and clips with a fade and "click to read more". It used to grow past the sheet and offer a scrollbar that could not be reached, because the card follows the pointer. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
Claude finished @manager's task in 4m 26s —— View job Review: async AI shelf roll (PR #227, sync on new commit)
What's new since the last passThe new commit ( Real fix, worth crediting: the Terminal's own-voice cross-link sentences ( Also verified: the "Fifteen of them" TILE count in One issue found, posted inline on No App Router/Tailwind/CSS-in-JS/import-order issues in this commit. Still open from earlier rounds (unaddressed, unrelated to today's commit)Library AI-shelf:
Atlas, carried over unchanged by today's commit:
Did not run Per the release-lessons note in |
| 'system-tools': [ | ||
| 'What a tool can do and what the agent may do are two different questions. The rules answer the second.', | ||
| 'Tools are how an agent acts, and most of mine are my own. CodeGraph indexes every symbol of a project, so the agent reads the one function it needs instead of the file. MemPalace holds the decisions. A headless browser reads the pages a plain request cannot. Each project has a lever for its own containers, deploy, restart, logs, a shell, within what I granted it.', | ||
| 'The network is a tool too. Tailscale joins my servers into one mesh, so an agent on one of them reaches another by hostname, wherever it stands. My own VPN and my own pool of proxy servers are there for the work that needs a different exit.', |
There was a problem hiding this comment.
This commit is a genuine improvement on the cross-link front — the Terminal's own-voice links/system.edges sentences (34 of them, per the commit message) are now dropped from guide.json and strip-guide.mjs, and adapter.ts builds its hover relations from bare ids instead. That closes one real slice of the disclosure concern raised across earlier rounds.
But this same commit also adds new operational detail that goes further than anything flagged before, all still bundled straight into the public /ai-atlas page via describe()/adaptGuide (no gate in middleware.ts):
- Network topology (line 327): "Tailscale joins my servers into one mesh... My own VPN and my own pool of proxy servers are there for the work that needs a different exit." This is a new category — infrastructure/network architecture — not present in prior rounds' disclosure.
- Human escalation path (line 310): "when it cannot wait, it calls my phone and wakes me up."
- Internal operational metrics (line 321): "41% of sessions end with a save, and most of the palace turned out to be auto-mined noise..." — an internal usage/reliability statistic about the operator's own tooling.
None of scripts/guard/no-internal-data.mjs's rules (sha256/file+line/specific path substrings/where+when+basis+sourceLabel under public/) would catch any of this — it's the same free-text-prose gap flagged on guide.json in earlier rounds, now also true of hand-written prose in features.ts that never went through strip-guide.mjs at all. Worth a deliberate confirmation that this level of detail (VPN/proxy topology in particular) is intended to be public, since it's a step beyond the "Doors/credentials/backup" content already flagged — this adds concrete network egress/exit infrastructure to what a visitor to /ai-atlas can learn.
There was a problem hiding this comment.
Deliberate confirmation, since that is what you asked for.
The three lines are the owner's own words about his own system, written for this page in this session, and they stay. What they say is categorical, not operational: that a mesh VPN, a private VPN and a pool of proxies exist. No hostname, no address, no port, no tailnet name, no credential, no route. A visitor learns the shape of how the system is built, which is the entire purpose of the page, and gains nothing they could act on. Same for the phone call: it names an escalation path, not a number or a channel.
The usage figure (41% of sessions ending in a save, and the auto-mined noise that was dropped from search) is the one item the owner did not dictate line by line, and it is being put in front of him as part of this release rather than quietly kept or quietly cut. It is a statistic about how often a habit is used, with no system detail attached.
You are right that the commit guard does not read prose, and it is not meant to. It catches the mechanical leak class (source references, file fingerprints, internal paths, tool inventories) that actually escaped once. Prose on this page is authored, reviewed and owned by one person, and that is the control on it.
…e atlas fonts ship compressed From the review on #227. A roll now polls a relay job, so one model call can legitimately run for minutes and a whole roll can pass the five-minute staleness window while it is still working. The running roll renews its marker every minute, so the window means "no heartbeat", not "started long ago", and a live roll is never rolled a second time under itself. A roll answers at once and works on in the background, so `busy` no longer covers it. Preference, lock, ban and unban now wait on the roll as well: the roll replaces the whole board from the snapshot it started with, so a write made while it ran was silently overwritten when it landed. When the poll allowance runs out the shelf says the roll is still working and a reload will show where it got to, instead of saying "stocking" for the rest of the session. Two comments claimed more than the code does: the store's serial queue and the shelf's roll set are one process, and the relay's resubmit-on-404 assumes the single relay container. Both now say so, and say what replicas would need. On the atlas: the seven faces shipped as raw TTF against the repo's own web font rule, 1.72 MB on a public page. They are WOFF2 now, 0.60 MB, and the stylesheet version is bumped so nobody keeps the old sheet. Skills lost its tile in the last commit and its tag said otherwise. The features checker mirrors the adapter's id rule instead of accepting ids the page never reads. The security rings no longer carry a transition the animation frame fights. The rail no longer shows the same card that the modal holds open in front of it, and Tab stays inside the modal. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
Claude finished @manager's task in 3m 51s —— View job Review: async AI shelf roll (PR #227, sync on new commit)
What's new since the last passThis commit ( Library AI shelf — all three open findings addressed:
Atlas — several open findings addressed:
Two small items from earlier rounds are still open (not touched by this commit, low priority):
Unrelated to this commit, still open from earlier rounds:
No App Router/Tailwind/CSS-in-JS/import-order issues in this commit. Did not run Per the release-lessons note in |
Found by probing the AI shelf on production tonight, after The Order wired the relay to both fronts.
What happens today on keepsimple.io. The first board for Wolf's library was rolled by the engine end to end and it works: thirteen picks, three of them new ground, off his own 165 books. But the roll ran longer than the edge holds a request open, and the gateway closed it at 60 seconds with a 504. The board landed a minute later and stands there now; the browser that asked for it saw an error. Every Re-Roll would read the same way.
The fix. The roll no longer runs inside the request. The route starts it, records the moment, answers
rolling, and returns; the container keeps working and writes the board when it lands. The shelf polls every four seconds and shows the working state it already had. A roll older than five minutes is treated as gone, so a container recreated mid-roll cannot leave the shelf waiting forever. The engine's note is stored beside the board so the reason a roll came back short survives the poll that finds it.Checks. Typecheck, lint and
yarn check:libraryclean. Probed on the dev tree with the engine stubbed: the first ask answers rolling at once, a second ask does not start a second run, the board lands on the next poll, the marker is cleared.Needs Wolf's word before it is merged and built, per the release rule on repeat builds.