Skip to content

Release v3.17.2 - #523

Merged
mtaanquist merged 576 commits into
mainfrom
release/v3.17.2
Jul 30, 2026
Merged

Release v3.17.2#523
mtaanquist merged 576 commits into
mainfrom
release/v3.17.2

Conversation

@mtaanquist

Copy link
Copy Markdown
Owner

One fix on top of v3.17.1: the help articles now render shortcut combos with the reader's own modifier labels - Cmd and Option on Apple platforms, Ctrl and Alt elsewhere - and the per-line "(Cmd on a Mac)" asides are gone (#522).

🤖 Generated with Claude Code

mtaanquist and others added 30 commits June 7, 2026 20:49
* SMTP: stop tying the TLS toggle advice to port numbers

Some relays (PostStack, for one) want implicit TLS on 587, so the old
hint sent admins the wrong way and the connection died waiting for a
greeting. The hint now defers to the relay's instructions, and a test
send that fails on the greeting or a TLS handshake says to check the
toggle.

* Admin: ask where the test email should go

The test went silently to the signed-in admin's address. The form now
shows the recipient, prefilled with that address, and the result
banner names it.
Notes: the long-disabled Notes segment becomes a real view at story and
universe scope. New notes table (migration 0048) carries all four scope
FKs; the UI creates universe and story notes (scene/chapter attachment
reserved). Pinned/recent list, prose editor with autosave, pin/delete,
and full History (preview + restore) reusing the revision machinery -
'note' was already in the revisions enum, so this adds the note branch
to ownedEntityBody/restoreRevision and to both REVISABLE lists. The
story view peeks universe notes under "From the universe". Cascade
cleanup is wired into deleteStoryWithin and purgeUniverseWithin (so
account deletion is covered through the universe purge), and notes ride
the story, universe, and account exports under notebook/.

Duplicate scene: a row-menu "Duplicate scene" makes a full copy (title
plus "(copy)", body, status, summary, planning fields, and markers
re-anchored) directly after the source, the building block for keeping
a scene as a reusable template.

Help docs and tests (integration + e2e) cover both.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
docs/SELF-HOSTING.md is the operator guide the README points to: a full
environment-variable reference, reverse-proxy examples for Caddy, nginx,
and Traefik, email and S3 storage setup, the backup and restore drill,
health checks, updating, a single-replica scaling note, and
troubleshooting.

In-app help gains three articles - account (profile, public page,
appearance, editor and page-setup defaults), security (password, email
change, two-factor, passkeys, sessions, export, deletion), and a
keyboard-shortcuts reference - registered in the docs index, and the
account page's help button now opens the relevant one per section.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Autosave (scene, entity, note) and asset upload had no rate limit, unlike
auth, search, and review. A shared write-guard helper applies two per-user
budgets through the existing in-memory limiter: a generous autosave bucket
(600/min, well above debounced multi-editor writing) shared across the
save endpoints, and a tighter upload bucket (60/min). Over budget throws a
429; the editor's debounced save chain retries, so a brief throttle only
delays a save.

The limiter is per-process; the single-replica stance this assumes is now
documented in docs/SELF-HOSTING.md.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
An account-level daily word goal (stored in users.preferences, account
Display section) shows today's progress on the Session tab and the
Insights page, with how many of the last 30 days met it. Each story can
set an optional target length and a deadline (migration 0049, additive
columns) in a new Goals section of story settings; the Insights Stories
block shows a progress bar toward the target and a due-in / overdue
line. Pure helpers daysMetGoal and daysUntil are unit-tested, with
integration coverage for the preference and e2e for the journey.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
#286: toggleInlineMark treated a single * beside the selection as an
italic mark even when it was the inner star of a bold **, so italicising
an already-bold word stripped the bold. An italic toggle now only unwraps
an isolated *; bold/italic compose to ***word*** in either order.

#285: add visible Cancel buttons to the dashboard create forms and
Esc-to-cancel to the two inline forms that lacked it (add-relationship,
universe-plan new story).

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The whole-story view is the editor, but with no toolbar it read as
read-only. Give it the shared formatting toolbar (acting on whichever
stitched scene editor holds the caret) so it plainly invites editing.

Add a read-only Preview beside it, rendered through the same
renderMarkdown the export uses: underlines, TODO and selection marks,
and the \center / \page literals are gone, with alignment, page breaks,
scene-break text, and paragraph style applied. The preview toggle lives
in the toolbar, not the top bar. Reachable as view=preview and via a
"Preview the story" palette command. Help and shortcuts docs updated.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat: read-only entity card in the editor's right column (#288)

Clicking an entity in the editor's "In this scene" list, or "Open full
details" on a mention's hover, now replaces the right column with a
read-only card (summary, description, typed relationships, details, and
an Open in Plan link) instead of navigating away to the plan editor.
Restores the prototype's EntityInspector.

Related entries open in the same card; Back walks the stack and returns
to the tabs from the first card. The card is fetched per id from a new
owner-scoped GET /api/entities/[id]/card (getEntityCard in plan-data),
so any related entity is reachable in the frame. The card closes when
the open scene changes.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* test: route core-flow find-usages through the entity card's Open in Plan

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…#295)

The "Read the whole story" / "Back to the scene editor" toggle and Focus
mode lived in the top bar, where they read as navigation. They are
editor-view controls, so move them to the right of the formatting
toolbar, next to Preview, and out of the top bar (which keeps search,
settings, help, notifications, and the account menu).

The toolbar shows while a scene or the whole story is open; in the empty
state focus mode is moot and the whole story stays reachable from Ctrl+K.
TopBar drops the now-unused storyView and onEnterFocus props.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The v2.41.1 release PR run flaked on the help modal: a lone click on the
help button can be dropped if it lands as the top bar re-renders, so the
dialog never opened. Retry the open until the dialog shows (opening is
idempotent). The same pattern fixes the entity-autocomplete popup, which
could open unfiltered if Ctrl-Space landed before the typed text
registered - re-ask until the filtered result shows.

Test-only; no app change.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
mtaanquist and others added 28 commits July 29, 2026 08:40
Prettier 3.9 on develop breaks these lines differently.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The Review tab re-anchored every comment and suggested edit with its own
character diff of the scene it sits in. A story whose scenes have moved on
since the notes were left pays that diff once per anchor: on a 45k-character
scene it is around 260ms each, and the load runs them back to back on one
thread, so the page hangs before it renders.

The threads and suggestions on a scene almost all share the same pair of
texts, so an anchor mapper now diffs each pair once and maps every anchor
through that one result, shared between listThreads and listSuggestions for
the page. On a restored production story (20 scenes, 98 threads, 53
suggestions) that takes the load's 26 diffs down to 5, and the load itself
from about 4.2s to 570ms.

reanchorRange and reanchorPoint keep their signatures and behaviour; the
offset maths moved into shared helpers so the cached and uncached paths
cannot drift, and the new tests check the mapper against them exhaustively.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Add the components sheet and the design pass briefs

Groundwork for the whole-app cohesion pass with Claude Design: a
components sheet audited from the shipped CSS and components, four
session briefs (landing, navigation, secondary surfaces, primitive
consolidation), and a CLAUDE.md pointer so page work starts from the
sheet.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Reconcile the design docs with the shipped app

design.md caught up with reality: the AI section describes the shipped
Assistant (roles, BYO key, egress policy, account toggle and story
mute), continuous-view editing, EPUB/PDF exports, the four sign-up
modes, the flat public shelf with links and commissions, and the
scene-mark preference; a new Beyond the writing surface section states
what review, insights, goals, notifications, print, import, and admin
are each for. assistant.md's status header now says shipped. Three
stale schema comments fixed. CLAUDE.md gains the keep-design-docs-
honest rule so the drift does not regrow.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Self-contained preview cards over the shipped CSS (styles/ is a symlink
into src/lib/styles), one card per canonical primitive from the
components sheet, each rendered in dark, light, and warm side by side.
Marked with @DSCARD groups for syncing into a Claude Design
design-system project; the README carries the sync steps and ground
rules. Verified by rendering every card headless and checking the
screenshots.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Latin subsets of Hanken Grotesk, Spectral, and JetBrains Mono (SIL OFL)
copied from the fontsource packages the app loads, declared in fonts.css
and imported by the kit scaffold, so previews render the actual
letterforms instead of system fallbacks. README sync steps updated,
including the symlink caveat found during the first push.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
The Codex design project now holds the CSS, cards, and fonts, so the
shared-context-plus-brief format collapses into four self-contained
prompts, reordered to the agreed run order (primitives, navigation,
secondary surfaces, landing) with delivery as pages in the project.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Adds primitives.css, loaded last, owning the modal, the empty state, the
chip remove affordance, and the shared focus ring. Completes .btn (states
plus btn-accept), gives .icon-btn a 28px .sm and a .danger tint, gives
.seg a .seg-count, and adds the type ramp, --danger-contrast, and a
per-theme --select-caret.

Migrates the scoped duplicates onto them: the rv-btn family, rv-quick-btn,
rb-btn, mini-btn, tool-btn, send-btn and pop-open become .btn/.icon-btn;
rtabs, rv-filters, rv-mtabs and revision-filter-chip become .seg; the three
hand-rolled modals share one panel; twelve empty-state spellings become
.empty-state. Deletes the superseded CSS and the drift sheet's dead blocks.

Behaviour is unchanged; e2e selectors follow the renames.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The modal primitive centres its panel, which made the palette's input
drift as results changed height while typing. A .top modifier on the
backdrop restores the 14vh anchor for overlays with changing content;
the review modal and help stay centred, their content height is fixed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* Land the accepted navigation model: one app bar for every page

Every page now wears the same bar - brand, path, tools - replacing the
editor's .topbar, the pages' .page-shell .topbar with its .back-link, the
print page's one-off bar, and .review-guest-bar. Three shells share it:
author, guest, reader.

The two contested decisions, as ratified. A place in the path is a menu:
clicking the universe or story name opens everything that belongs to it,
starting with "Go to the universe" / "Go to the story", settings included as
a word rather than a glyph. There is no gear anywhere in the chrome. And the
help modal is gone: the ? tool navigates to the help pages, lights up via
aria-current while you are there, and returns to where you were when pressed
again, so help is openable in another tab and never covers your work.

Paths are built in $lib/chrome.ts so every screen spells the same
destinations the same way. The theme tool cycles dark -> light -> warm
through the existing theme.ts, persisting to the account when signed in and
to localStorage for guests and readers; the avatar menu's theme item now
walks the same cycle instead of a parallel light/dark flip. The mode strip is
always four modes: at universe scope Write and Review open the universe's
story list, and for a guest three are switched off in place, each case with
one rendered line under the strip saying why.

Deleted: HelpModal, TopBar, PageTopBar, PaletteButton, and the .topbar,
.crumbs, .back-link, .breadcrumb, .save-status, .review-guest-bar and
.universe-edit CSS. /docs is public now, because the guest and reader shells
keep the help tool and neither has an account.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* Reach the library through the bar's brand, not the deleted .brand

The old bars carried .brand; the app bar's is .appbar-brand. This click sits
past the export step, which the local suite cannot reach without MinIO, so
only CI caught it. Selects by accessible name instead of by class.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* Note the navigation model in TODO.md

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* Say where the story list is, not where the mock had it

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
… one creation pattern (#508)

* Give the right pane one panel strip across every workspace view

* Build the reading pages from the tokens, with one footer and the reader's theme

* Give every collection one creation menu and retire the Session panel

* Move the tests onto the new panels, menus and reading pages

* Bring the help articles and the design docs up to the surfaces model

* Keep the review panel's props in one place, and steady three specs

* Note the pen-name-at-publish ask in the backlog

* Give the theme cycle a third icon: the button shows where the click goes

* Hide the review tab bar on desktop; it exists for narrow screens

* Remove Accept all: a bulk apply can change more than is on screen

The per-card accept stays. If bulk decisions come back they belong in a
review toolbar that shows its scope first (backlog note added).

* Never scroll the review manuscript sideways

A suggestion ghost's padding can poke a few pixels past the measure and
CodeMirror's scroller grew a horizontal scrollbar over it. The lines
wrap, so sideways scroll has nothing legitimate to show.

* Space the compose draft's textarea off its action row

As an inline element its only gap was the baseline slack, which read
as the buttons overlapping the field.

* Draw the focused review mark's ring inside its box

An outward ring on wrapped inline text rings every line fragment and
collides with its own next line, and with the ghost beside a deletion.
Inset ring with cloned fragments instead; the pulse animates the tint.
The editor's ghost also gains the 3px gap the read surface already had.

* Let a focused suggestion sit in justified prose

The ghost's pre-wrap kept its preserved spaces rigid, dropping every
line it touched out of justification; pre-line collapses them so they
stretch, while a newline in the suggestion still breaks. All marks
gain enough padding that the inset focus ring never sits on a glyph.

* Slice the review marks so focus never moves the prose

Cloned fragments gave every wrapped line the mark's padding and margin,
which indented continuation lines - and only on focus, so the text
jumped when a card was clicked. Slice keeps padding at the mark's true
ends only: wrapped edges sit flush at the margins, justified lines keep
their measure, and focusing changes paint alone.

* Keep marked prose on the measure

Three causes, one symptom each: an anchor that grabbed edge whitespace
tinted it (render-side trim now leaves it out, the stored anchor is
untouched); a space hanging at a justified line break painted its tint
past the measure (the line clips it); and the mark's own edge padding
pushed its text off the shared left edge (a matching negative margin
hands the width back, so the box bleeds and the text does not move).

* Say Cmd, not Ctrl, on Apple platforms

The palette already accepted both modifiers; only the labels lied. A
keys helper names the platform's modifier, and the app bar chip and
the formatting tooltips settle on it after mount (SSR says Ctrl).
…gned-out page (#509)

* Port the public surfaces: one shell for everything a visitor sees

The fourth design-pass session. public.css is a new layer loaded last and
the .landing-* block is gone from pages.css.

One public shell now covers the landing page, sign-in, sign-up and every
page an email link lands on: .public-surface over AppBar's fourth
reduction (shell="public": brand linking home, theme and help, no path)
and ReaderFooter's public reduction. AuthShell is the card inside that
shell rather than a frame of its own.

The landing page shows the workspace instead of describing it: a .ws-*
figure drawn from tokens, so it is correct in all three themes, with a
real three-way posture tablist carrying the same keyboard contract as
PanelStrip, then four model statements in sentences.

Sign-in keeps email and password, the passkey button and forgot-password.
Sign-up keeps its fields and every mode and sent state. The open/closed
switch is now mode-aware through signupOffered(): approval and open offer
sign-up, invite and none do not, because a visitor with no code cannot
finish one.

New help article on self-hosting, so every handbook link on these pages
reaches an article that exists.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Give invite mode its own account statement on the landing page

The invite branch rendered the approval paragraph (ask and you get an
answer) and then said requests are closed, which contradicts it. One
paragraph per mode.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Link the sign-up page from the invite-mode text

A code sometimes arrives without a link; the prose now carries one on
the landing page and the sign-in page, noted in the design docs.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Let the public text fill its boxes, and link the footer brand home

The drop capped every prose block to a measure; inside the model cards
and under the heading the box already bounds the line, so the caps only
left dead space. Caps stay, loosened, on the two full-width prose
blocks. The footer brand on public pages now links home like the bar's.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Preload route code on viewport, not hover

Hover preloading inserted modulepreload links into the head mid-hover,
and Safari and Firefox only recompute the cursor on mouse movement, so
the hand cursor dropped to an arrow a moment after entering any link.
Code now preloads when a link scrolls into view; data stays on hover,
since a fetch does not touch the DOM.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Match the footer brand link exactly

getByRole matches the accessible name by substring, so 'Codex' also
resolved 'Reading in Codex' and the click failed strict mode.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Format the footer brand locator

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* Save account Profile and Assistant settings on field exit

The last two account sections with explicit save buttons join the
autosave pattern the other four already use. One-shot actions (claim
handle, enable publishing, test connection, discover models) keep their
buttons. autosubmitForm skips data-no-autosave controls so the model
filter box does not save; a discovery result now outlives the next
action result so role picks after a discover keep the full list;
removing a profile link saves itself.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Keep the alternating test name matching the shelf assertion

core-flow reads the anonymous shelf name for the 'Tester' substring;
'E2E Retester' only contains it lowercased.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* Move story import into a modal with drag-and-drop

Importing from the library no longer lands on the settings page next to
the danger zone. The New menus and the settings Import block open a
modal instead: pick or drop a file (zip, docx, epub), the preview runs
at once, then confirm. The form posts to the target universe's existing
actions from wherever the modal is mounted, so the server flow is
unchanged.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Drive the Word-manuscript import spec through the modal

Missed when import-story.spec moved: this one now exercises the
settings-page entry point.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
The library card leads with the reader shelf's cover pattern: the
uploaded artwork, or the title set on the cover blank. The library load
gains cover_asset_id.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
The mode strip drops Notes (Write / Plan / Review remain); the Notes
page sits beside the modes with nothing lit and a line saying how to
get there. The Write view's Notes panel is now always present, listing
the story's notes with the open scene's first, and both Plan views gain
a Notes panel for the open entry's scope. The command palette learns
Story notes, Universe notes, and Quick note.

Quick capture: Ctrl/Cmd+Alt+N floats a jot card over the editor; the
first line becomes the title, Ctrl+Enter saves to the story's notes
(attached to the open scene when there is one) through a new
POST /api/notes, and focus goes straight back.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
An admin sets a per-user invite allowance from the accounts list
(migration 0066, additive). Users with one get an Invites section on
the account page: generate single-use codes against the allowance,
copy the sign-up link, and revoke unused codes to free their slot.
Codes redeem exactly like admin-minted ones, so an invited friend
skips the approval queue but still confirms their email.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Claiming a handle or publishing an edition with no pen name set opens
a prompt asking what name goes on the page, offering the display name
to keep. The choice is stored as the pen name (setPenNameIfUnset, which
never overwrites), so the public byline is always one the author chose
and the prompt never returns.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
The /@handle shelf now shows the owner their own header (bio, links,
avatar, commissions) even while the profile is not public, with a note
saying only they can see it. Visitors are unchanged. The account page
links to the page from the address field.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
The quick-note hint and the editor's TODO hint hardcoded Ctrl and Alt;
they now read the platform labels from the existing keys helper, which
gains altLabel beside modLabel. The shortcuts article's convention line
covers Option too.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
The articles write shortcuts with the PC labels; the docs page now
swaps Ctrl+ for Cmd+ and Alt+ for Option+ for Apple readers, through
appleShortcutLabels in the keys helper. The per-line '(Cmd on a Mac)'
parentheticals go, and the shortcuts article's convention line says
what the reader actually sees.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
@mtaanquist
mtaanquist merged commit a4d0228 into main Jul 30, 2026
6 checks passed
@mtaanquist
mtaanquist deleted the release/v3.17.2 branch July 30, 2026 21:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants