Repository navigation
Add ledger and bands views, and a harness to inspect them - #4
Merged
Merged
Conversation
Two modes for the same projection, both additive: `phases`, `list` and `compact` emit byte-identical markup to before, pinned by a test. - `ledger` — one column, phase order intact, three fixed cells per item (glyph, title+summary, status) so status stops competing with the title for the eye. Shipped phases collapse to a single line carrying gets_you, which no view rendered until now despite every phase defining it: for a prospect it is the payoff, and it says more in one line than a card of feature names. - `bands` — one full-width band per status in urgency order, each item tagged with the phase it came from. `in-progress` and `blocked` keep their band when empty, because an empty one is the news. `view-model.ts` gains the grouping (`statusGroups`, `STATUS_ORDER`) and a phase back-reference on `ItemView`, per the rule that the view model decides what to show; both renderers stay thin and mirror each other. The status column is `max-content`, not a fixed width: German "vorgeschlagen" overflows an 8rem cell, and a right-aligned nowrap cell overflows leftward over its neighbour. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…olumn A roadmap has to hold up across five view modes, every locale the file declares, both audiences, both grounds and any width. Nobody checks that space by hand, so `nextup serve` now renders one combination in an iframe with the axes in a toolbar above it. The state lives in the query string, which makes a single combination a link — the useful currency when the answer is "look at THIS one". It reuses the existing SSE stream and reloads only the frame, so the toolbar keeps its scroll and focus. The harness paid for itself immediately: `ledger` and `bands` were broken below roughly 640px. Their fixed grids do not reflow, so on a phone the title, the phase tag, the status word and the links all landed on top of each other. Both now collapse to glyph + stacked content. That breakpoint is a container query, not a media query: this component is a block on someone else's page and is as likely to be narrow in a sidebar on a desktop as it is on a phone. Forcing a ground needed the dark palette to exist outside its media query, so both palettes are now declared once in `styles.ts` and emitted three ways — element defaults, `prefers-color-scheme`, and a `data-nextup-theme` attribute any host with its own toggle can set. Declaring them once is what stops the forced ground from drifting from the automatic one. `parseHarnessState` validates every axis rather than trusting the query string; an unknown view mode must never reach the renderer as a class name. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The harness showed nextup's own palette — green accent, blue "now", 10px radius, system font — which is exactly what no consumer ships. lexilink's look lives entirely in the app: a values-only theme.css, a roadmap.css that maps those tokens onto --nextup-*, a wrapper class the mapping is scoped to, and a `.dark` class instead of prefers-color-scheme. The preview could not answer "how will this look in lexilink", which is the only question worth asking of a view change. `nextup.config.yaml` now takes an optional `skins:` list, and the harness gains a Skin axis with the package defaults always offered alongside, so a skin can be compared against them. All four parts matter: css in declared order, the wrapper (a mapping scoped to a class does nothing without it), the host's dark class, and font links. serve-only, and deliberately: `build` never reads skins. A projection carries no styling and a skin must not be able to change what is published. Skin paths come from a config file, so they resolve against the repo root and are refused if they escape it — a dev server should not become a way to read arbitrary files through a config someone pasted in. A skin naming a moved file degrades to the defaults rather than 500ing. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A bare /harness selected the package's own look, so with a skin declared you still landed on exactly the design the skin exists to replace — the picker was there, but you had to find it and switch. Previewing is done for a host: the first declared skin wins the default, and nextup's own palette stays selectable as a deliberate comparison. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Two new view modes for the same projection, plus the surface to judge them on.
Views
ledger— one column, phase order intact, three fixed cells per item (glyph, title+summary, status) so status stops competing with the title for the eye. Shipped phases collapse to a single line carryinggets_you, which no view rendered until now despite every phase defining it.bands— one full-width band per status in urgency order, each item tagged with the phase it came from.in-progressandblockedkeep their band when empty, because an empty one is the news.Both are additive.
phases,listandcompactemit byte-identical markup to before, pinned by a test that strips theis-view-*class and compares the trees.view-model.tsgains the grouping (statusGroups,STATUS_ORDER) and a phase back-reference onItemView, per the rule that the view model decides what to show; both renderers stay thin and mirror each other.Harness
nextup servenow serves/harness: one combination of view × locale × audience × width × ground in an iframe, with the axes in a toolbar and the state in the query string, so a single combination is a link. It reuses the existing SSE stream and reloads only the frame.It paid for itself immediately —
ledgerandbandswere broken below ~640px, where the fixed grids do not reflow and the title, phase tag, status and links landed on top of each other. Both now collapse to glyph + stacked content, behind a container query rather than a media query: this component is a block on someone else's page and is as likely to be narrow in a desktop sidebar as on a phone.Skins
The harness first showed nextup's own palette, which is what no consumer ships.
nextup.config.yamlnow takes an optionalskins:list — css in declared order, the wrapper class a host scopes its tokens to, the host's dark class, and font links — and the harness gains a Skin axis defaulting to the first declared one.buildnever reads skins: a projection carries no styling, and a skin must not be able to change what is published. Skin paths resolve against the repo root and are refused if they escape it.Forcing a ground needed the dark palette outside its media query, so both palettes are declared once in
styles.tsand emitted three ways — element defaults,prefers-color-scheme, and adata-nextup-themeattribute any host with its own toggle can use.Checks
bun run lint, all three tsconfigs, 136 tests andbun run buildgreen locally.🤖 Generated with Claude Code