Commit 4b9b47b
authored
Themes, fonts, reader typography, weave membership, Study hub (v0.49.0) (#26)
* UAT round: hymnal bar, read gates, plan chip + pause, viz grouping, portrait weave map (v0.49.0)
Reader & reading map:
- A destination no longer wears the reader's passage: the header subtitle
hides off the Read screen (the Hymnal advertised "1 Corinthians 7").
- Reading is tracked only with the text in front of the reader: Android now
also gates on Present and the map overlays (web already did).
- A chapter completes at 85% dwell AND the last verse reached — the snap is a
tolerance on the clock, not permission to skip the end of the chapter.
- Overscroll (last line to the top of the pane) is a phone affordance: web
keeps it under the 700px breakpoint and stops it AT the last line, so the
text can never slide off entirely; Android same stop, all its modes.
Plans (web UI; core+ABI shared):
- The nav-strip chip retires for the rest of the day once a full plan-day
finishes today — even when it was yesterday's leftovers (plan::done_today,
doneToday on the plans wire, dated by the chapters' last_read).
- Plans pause and resume (paused in the plan file, additive; new
plan_set_paused ABI fn): a paused plan keeps its progress and class but
asks nothing — no chip, no today card — and its card reads name + start
day ("Started 3 Aug 2026 · paused").
Study hub:
- One ≡ above 700px: the ScreenBar's menu hides where the app header (with
its own ≡) is still visible — two stacked menus in different colours.
- Constellation and Weave map fold under one expanding Visualizations card
(both shells) — two views of one thing, not two more tools.
Maps & boot:
- The weave map flips in a portrait viewport (both shells): canon axis down
the left edge, ribbons bulging right, taps mapped y→book.
- The canon strip carries its own background from the first frame and fades
its detail in — no more differently-coloured bar the minimap pops onto.
Also: README rearrange + the themeable screenshot (maintainer), version to
0.49.0, FEATURE-MANIFEST updates, EN+DE strings, bindings regenerated.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
* Lint: clippy useless-vec in the new reading test + rustfmt reflow
🤖 Generated with [Claude Code](https://claude.com/claude-code)
* Notes copy, theme roster rework, Atkinson Hyperlegible (stacked on #26)
- Notes card copy (pick C): "Browse every note you've left in the text."
("search" trimmed — the notes browser has no search field yet, and the
card must not promise one.) EN + DE.
- Darcula retired as a near-duplicate of One Dark. Both parses ALIAS the
stored token to one-dark, so an existing config opens on its nearest
neighbour instead of snapping to System. Pinned by test.
- Four house themes, none of them editor ports, all clearing the same
WCAG-AA contrast test as the rest (18 themes now):
scriptorium — parchment, iron-gall ink, RUBRICATED accents (light)
blueprint — cyanotype: Prussian blue, chalk ink, gold divine name
phosphor — green CRT with the classic amber second phosphor
high-contrast — pure black on white, the deliberate low-vision option
- Atkinson Hyperlegible (Braille Institute, OFL) as the fifth face, both
shells, optical scale 0.90 (x-height 0.496, same half-correction as the
others). It is the one STATIC family, so Font::static_bold enters the
vocabulary: the web declares each face at its single weight (a static 400
declared 400-700 paints bold text regular), keeps the 700s out of the
engine worker's measurement list, and precaches them via the new
FONT_ALL_FILES; Android's FontSpec carries bold/boldItalic asset paths.
Whole family: 87 KB woff2.
- BIBLIOGRAPHY credits all five faces; FEATURE-MANIFEST updated.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
* Verse-number + supplied-word-italics switches, and weave membership on a verse
Three asks, and the first two are NOT the same kind of thing — which is most
of the work here:
- VERSE NUMBERS OFF is a LAYOUT input (`LayoutConfig.verse_numbers`, and +4
bytes on the by-value `PlumblineLayoutConfig`). A shell that merely declined
to paint them would flow every verse around an invisible marker: the number's
box AND the gap after it belong to the line. So the core emits no number
items and reclaims the width, and the field joins both turn-cache keys
(`engine.worker.ts`, Android's `ChapterKey`) while staying out of
`font_identity` — like `verse_break`, it moves boxes, never a glyph's advance.
The web hand-marshals the struct, so its size moved 28 → 32 in lockstep.
- SUPPLIED-WORD ITALICS OFF is PAINT ONLY. The measure callback is font-blind,
so the engine already measures those words upright and the layout is
untouched: deliberately absent from both cache keys, present in Android's
ChapterPaintKey (the Picture bakes the face) and named as an explicit
dependency of the web's paint effect. That last one was a real bug the new
e2e caught: `draw` runs in a rAF callback, outside the effect's tracking
scope, so reading the flag down there registered nothing and the italics
stayed on screen until something else happened to repaint.
- A VERSE NOW NAMES THE WEAVES IT IS IN, above the partner list and separate
from it. The two answer different questions and the partner list buries the
first: a verse with six links into one weave read as six rows repeating a
name, and a verse in three weaves gave no way to see the three at all.
Both switches default ON and are written as explicit booleans, so every read is
`!== false` — an absent key is a config from before they existed, not an opt-out.
Tests, each mutation-tested against the defect it describes:
- core: numbers off emits no boxes AND reclaims their width (the mutation that
keeps the gap moves the first word to x=4, and the test fails);
- core: membership dedupes by name across an interleaved partner list, is
singular at one and absent at none;
- web e2e: both switches driven through Settings › Advanced, asserting the
display list for numbers and the CANVAS PIXELS for italics — dropping the
paint dependency or the cache-key field each kills exactly its own test.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
* Study hub: an In-progress band, live counts, and the reading map as a bar
The hub was eight identical rectangles of FIXED text — the same on install day
as after a year of study. Plans tells you today's chapters; the Hymnal is full
of hymns; this screen had nothing that could change, which is what "a bunch of
boring brown cards, not like the other pages" was describing.
Informative first, per the ask:
- an IN PROGRESS band, drawing only rows that have something to say (a hub
reading "0 due · 0 to review" every day would be the fixed text again): the
running plan with today's chapters, the memorize queue when anything is due,
the review queue when anything waits. Nothing running at all gets one
invitation rather than an empty box, and a PAUSED plan says so and names no
chapters — promising chapters it is not asking for reads as asking for them.
- a count on every card that holds a collection, absent at zero so an empty
tool reads as quiet rather than as a score of nought. Plans and Memorize
carry none: they are activities, and the band speaks for them.
Visual, as the bonus and where it costs nothing: the reading map as one number
and one bar — chapters read of 1,189, the map's own readDone over a faint
readUnread track, so it is correct in all eighteen themes for free and taps
through to the navigator. The first cut used the unread gold at 22% and an
unread reader's empty bar looked FULL; it is 12% with a hairline now.
No new engine calls: every number is a query another screen already makes, so
the web reads them through the session cache and they move on an authoring
write. That is the failure worth pinning — a count fetched once looks perfect
until the reader writes something — so the e2e writes first and then looks, and
the mutation that snapshots the counts instead of deriving them kills exactly
that test and no other.
Two traps this hit, both worth the note: the band first rendered as
EXPLORE.INPROGRESS because the i18n catalogue lives in the ENGINE and only the
bundle had been rebuilt; and the "quiet at zero" assertion first passed because
`q` answers null before its fetch lands, which is indistinguishable from an
empty tool — it now waits on the stock set's own weave count as the signal that
the queries have resolved, and asserts a DELTA where the stock set seeds data.
Android gets the same band and counts (plans stay web-only, per the manifest's
shell deltas), refetching on the note epoch and on every entry to the hub.
🤖 Generated with [Claude Code](https://claude.com/claude-code)1 parent 8b03aa1 commit 4b9b47b
70 files changed
Lines changed: 2503 additions & 232 deletions
File tree
- apps
- android/app/src
- main
- assets/fonts
- java/dev/plumbline
- ui
- test/java/dev/plumbline
- web
- e2e
- fonts-src
- public
- fonts
- scripts
- src
- engine
- lib
- maps
- reader
- shell
- state
- assets/readme
- crates
- core/src
- i18n
- panel
- ffi
- bindings/kotlin
- include
- src
- layout/src
- docs
Some content is hidden
Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
94 | 94 | | |
95 | 95 | | |
96 | 96 | | |
97 | | - | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
21 | 21 | | |
22 | 22 | | |
23 | 23 | | |
24 | | - | |
| 24 | + | |
25 | 25 | | |
26 | 26 | | |
27 | 27 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
38 | 38 | | |
39 | 39 | | |
40 | 40 | | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
41 | 46 | | |
42 | 47 | | |
43 | 48 | | |
| |||
116 | 121 | | |
117 | 122 | | |
118 | 123 | | |
119 | | - | |
| 124 | + | |
120 | 125 | | |
121 | 126 | | |
122 | 127 | | |
| |||
233 | 238 | | |
234 | 239 | | |
235 | 240 | | |
236 | | - | |
237 | | - | |
238 | | - | |
239 | | - | |
240 | | - | |
241 | | - | |
242 | | - | |
243 | 241 | | |
244 | 242 | | |
245 | 243 | | |
| |||
312 | 310 | | |
313 | 311 | | |
314 | 312 | | |
| 313 | + | |
| 314 | + | |
| 315 | + | |
| 316 | + | |
| 317 | + | |
| 318 | + | |
| 319 | + | |
315 | 320 | | |
316 | 321 | | |
317 | | - | |
| 322 | + | |
318 | 323 | | |
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Lines changed: 92 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
478 | 478 | | |
479 | 479 | | |
480 | 480 | | |
| 481 | + | |
| 482 | + | |
| 483 | + | |
| 484 | + | |
| 485 | + | |
481 | 486 | | |
482 | 487 | | |
483 | 488 | | |
| |||
0 commit comments