Second Pass — UI Notes

Your six annotations and two margin notes, plus the three refinements the handoff was holding.

1 August 2026 · branch stellar-projections · build green · 4/4 tests · stellar verify exit 0

Two of these were not taste — they were bugs with provable causes

The active nav tab was painting #171715 on #171715. Not low contrast. The same colour. clean — the design system the game category uses — sets gold: #171715 and declares no band or band_accent, so the strip's background fell back to ink and its active accent fell back to gold, which is the same value. Inactive tabs render from paper, so they were fine. The active tab was the only one that disappeared.

Your SPOTS question has a factual answer, and it was in the exporter. You asked how important it is to fill those in. spots is optional by design: export-machine.py guards it (if r["spots"]:) and Stellar's Spot.swift:31 says outright that base rotations author none, because at base the spot is the zone. near_spots is conditional — the export hard-fails with "has near_spots but no spots to be wrong about". 12 of 18 whiteboard entries have spots and 6 of 18 have near_spots. That is correct, not debt.

Sidebar parity

01

Commit is local nownew

The orange count in each guide row was inert — it told you which project was dirty and then made you use a global button that swept up everything. It is a menu now: Commit N Changes in Stellar, scoped to that guide's paths only.

One thing I did not let it swallow: changes under framework/ and categories/ belong to no guide. A purely per-guide commit would strand them silently, so the corpus chip names them — "12 uncommitted · 3 outside a guide" — with its own commit action.

Where to look: click the orange number beside a guide. The corpus chip appears under the title only when something is uncommitted or unpushed.
02

The app says its own name

"Press Raven" existed only in the window title bar, so the sidebar opened on a bare Dashboard with nothing above it. There is now a header row with the logo and the name.

03

Export and New, at the topnew

Export (the share glyph) opens the export modal. The + holds New Seed and one entry per kind.

I also put a + on each kind heading, because with the taxonomy in place New Project and New Guide are genuinely different acts and a single global + said neither. The sheet now knows its kind: it offers only that kind's categories, titles itself accordingly, and skips the category picker entirely when the kind has just one (Game is the only project category, so asking was a click to dismiss).

Where to look: the two icons top-right of the sidebar header; the small + on Catalogs / Projects / Guides. The old sidebar toolbar is gone — everything it held moved here.

The document

04

The active tab is visiblebug

Cause above. onBand now falls through to the contrast-checked pole when its accent does not separate from the band it sits on — the machinery already existed for the masthead (onBandInk) and this reuses it. I also dropped inactive tabs from 0.85 to 0.55 opacity and made the active one heavier, so active and inactive are not two shades of the same white.

DesignBandActive accent, beforeVerdict
clean ← games#171715#171715invisible
commercial#1f2937#64748bmarginal
the other 13variousvariousfine, unchanged
Where to look: Stellar's strip. BRIEF / THE THREAD / STORIES / THE SIGNAL / THE RALLY / BLOW THE WHISTLE — the one you are in should now be the loudest, with an underline.
05

Ways In is no longer a menunew

You said it looked like system chrome, and it was: a stock macOS popup with a door glyph, in a toolbar whose whole job is artifact fidelity — sitting top-right while you work four hundred entries down.

It is now a strip anchored to the foot of the document, drawn entirely in the guide's resolved tokens (paper_raised, rule, accent), so it belongs to the page. Always in reach regardless of scroll. It hides in read mode, on frozen guides, and while a sculpting session owns the foot of the document.

I rejected putting it in the nav strip on its own terms: that band only renders when a guide has two or more sections, so a brand-new guide would have no doors at all — precisely when you need them most.

Where to look: the bottom of any editable guide. The toolbar menu is gone; this is the only home now.
06

Each mini game states itself where its entries are

The roster at the top is the index; scroll into the third mini game's two hundred items and the heading was a bare name. Its mechanic and description now sit directly under the section header.

Where to look: scroll Drive Corps to Intercept, or Stellar to Blow the Whistle. The mechanic in small caps, the reskin line beneath it.

Your margin notes

A

You can pick entries nownew

You were right that it did not exist. The navigator's list was bound to a single optional id, so the only scopes were "one entry" and "a whole group" — nothing in between.

It is a multi-select list now (⌘-click, ⇧-click). Picking one behaves exactly as before. Picking two or more raises a bar: Session…, Packet…, isolate-in-document, and clear.

The packet path already accepted an entry subset. The session path did not, and threading a scope through all five phase contexts would have been out of proportion — so the packet gains an explicit THE ENTRIES I'M ASKING ABOUT block that renders the picked entries in full and tells Claude the rest is context, not subject. It reuses PacketService.entryMarkdown rather than adding a second renderer that would drift.

Where to look: ⌘-click three entries in the navigator. Both buttons are scoped; the launcher's subtitle says "3 picked entries".
B

Empty fields stop shoutingneeds you

Two halves. The app half is done; the corpus half is written, tested, and waiting — see below.

The app. Empty fields that are not required collapse into one quiet line: "2 optional fields empty — spots, near spots". Click it, or the ellipsis menu's Show Empty Optional Fields, to bring them back. It names what it hid rather than hiding it silently — silent omission is exactly the failure mode that buried your 87-entry rules bank.

The corpus. required has always existed on every field, and 9 of 226 fields in the whole corpus set it — none of Stellar's 77. So the app had nothing to believe. tools/declare-required.py reads requiredness off the exporter rather than off my opinion: a field is required when its builder subscripts it unguarded (e["situation"] — KeyError without it), optional when the builder guards it (e.get("spots", {}) — defined behaviour without it). It marks 31 fields across 8 mini games, and leaves tag-the-photo, the-streak and called-shot alone because nothing consumes them yet, so there is no evidence to read.

whiteboard required:  correct_positions, phase, rotation, verification
whiteboard optional:  near_positions, near_spots, provenance, spots, voice_source

This is the one thing waiting on you

Press Raven is running, and editing schema-extensions.json reloads the schema under you mid-audit. I did not want to do that without asking, so I proved the script against a scratch copy instead: 31 fields marked, re-run is a clean no-op, and the result is structurally identical to the original apart from the added required keys.

Say go and I will run it. Until then the app half is live but has nothing to act on, so entries look exactly as they did.

From the handoff

07

The app opens where you left off

Last-opened guide, restored on launch. It lives in UserDefaults, not the corpus — the Prime Directive governs content, and which guide you had open is session state; delete the app and nothing is lost.

Restored on corpus load rather than on appear, because guides arrive asynchronously and an on-appear restore would run against an empty list and silently fall back to the dashboard. First run still opens on the dashboard.

What I could not check

Screen Recording and Accessibility are still both denied to the CLI, so screencapture and UI scripting both fail. I have not seen any of this. It is verified by compile, 4/4 RavenKit tests, a headless corpus load with no errors, export-machine.py stellar --verify exiting 0, and the gap report regenerating with 0 bad references — but every visual claim above is an inference from the code, not an observation. Your audit will find anything visually wrong before I do.

Both repos have uncommitted changes; I have not committed or pushed. The corpus is untouched.