Repository navigation
release(0.15.0): the market-demand bucket, hardening round, pipeline debt - #511
Merged
Merged
Conversation
…(B11.21) The gutter + now opens the host's slash catalogue as a filterable picker through the shared search picker, anchored to the button (the gutter is pinned while it is open), and inserts the chosen block below. The block action menu gains Insert above / Insert below rows, and Mod+Shift+Enter opens the same picker below the caret's block. Dismissing the picker leaves the document untouched; without a menu host + falls back to an empty paragraph. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…B11.22) A transcluded body was a read-only mount of the source's Y.Doc. It now carries an Edit here / Done toggle: in edit mode the nested editor is editable and mounts the Notes editing plugins (slash menu, toggles, callouts, embeds, mentions, references, link markup, inline toolbar), so edits land in the SOURCE's doc behind an accent boundary, with the source card as its chip. The source's denormalised title/snippet/refs follow through the same lock-gated update funnel the open note uses. Read-only stays the answer when the host page is read-only, the source isn't loaded in Notes, or the source is locked (disabled toggle with a hint; a lock landing mid-edit drops back live). Undo is the source's own UndoManager. Document-scoped editor chords now ignore keydowns from a nested Lexical root so a host plugin never acts on its stale selection while typing in the nested editor. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…aware embedded block (9.12.24)
- sdk-types: { $host: true } PropertyRef, allowed on $contains/$notContains;
predicateReferencesHost so a host-relative predicate with no host matches
nothing as a whole (negation can't widen it).
- predicate-eval: PredicateContext { hostEntityId } threaded through
evaluatePredicate + evaluateSource; $host resolves to the literal id.
Seeded property tests: no host => never matches; host => literal-id parity.
- shell entities-repo SQL compiler: $host (and computed $contains RHS)
compile to match-nothing, never match-all.
- block channel: optional hostEntityId in the BP Startup payload, validated
on both ends (sanitizeHostEntityId); BlockRuntimeContext.hostEntityId();
BpBlockMount prop; editor BlockEmbedView derives it from the render chain.
- embedded-list block resolves rows via the List's real source + member
overrides + default view filter/sort with the host context.
- filter builder: FilterRefKind enum (prop/now/host), 'this object' RHS for
relation properties on is/is not/contains/does not contain.
- entity-draft: $host pins pre-fill the host into a new row's relation.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…es, so 'relation contains this object' works on multi relations Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A real block-level background for paragraphs, headings, quotes,
callouts, toggles and list items: the block action menu gains a
Background section (one row per swatch). Only the swatch NAME is stored,
on the element's own style (synced by @lexical/yjs, carried explicitly
through duplicate and the block clipboard, re-validated on paste), and
BlockBackgroundPlugin — mounted by BrainstormEditor so previews and
nested bodies paint it too — mirrors it to a data attribute that
editor-theme.css maps to a token.
The tokens are new: color.blockBackground.{gray..red}, defined in every
built-in theme (light washes / dark tints by appearance), with a contrast
ratchet holding text.primary at AA on each, and the canonical token-name
snapshot re-synced (TOKEN_NAME_VERSION 4). Table cells get a Cell
background submenu over the same tokens; hasCellBackgroundColor is on.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- sdk-types: optional inverseOf on PropertyDef; validatePropertyDef accepts it on entityRef only. - shell inverse-relations planner (pure): symmetric pair map (ambiguous pairs ignored), add/remove mirroring, single-valued far-end displacement, delete cleanup, one-time backfill; preserves stored value shapes. - entities service: create/update/delete plan far-end writes from the live catalog, check every far end (entities.write on its type + read-only lock) BEFORE writing, then apply through the same Y.Doc-first write path. - properties service: a def that newly declares inverseOf backfills first (as the calling app) and is saved only if the backfill was allowed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…11.24) /task names the task in the shared search picker and, on Enter, client-mints a task id, drops an @-mention of it at the caret and creates the brainstorm/Task/v1 entity (rolled back if the create fails). Because the chip IS a mention, the note -> task reference the shared walker reads is the task's backlink, and Graph edges, clipboard and Markdown export need nothing new; the Tasks app sees the entity via its live-entities stack. Every mention of a task now carries a live checkbox (the SDK Checkbox): done state comes from the shared entity index (new getEntityProperties), completion writes through a new EditorHost.entities slice with the same patch the Tasks inline-task block writes, and it stays disabled for a locked task, a read-only editor or an unwired host. Notes wires the host under its existing entities.write:* grant (no new capability); FullEditorPlugins offers /task only when a host is wired. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A window can now host a second object beside its active tab, each pane its
own sandboxed app renderer registered under its own app id, so the broker
keeps attributing every call to the right app even when the two panes are
different apps.
- WindowContainer: a side pane next to the strip, laid out from a pure
state machine (window/split-state) + geometry (window/split-layout):
ratio clamp to a 280px minimum per pane, keyboard step, swap, focus
tracking. The window title follows the focused pane; Cmd+W in the side
pane closes it; closing the primary's last tab promotes a same-app side
pane, or re-homes a different-app one into its own window with its
renderer (and identity) intact.
- Divider: the chrome view spans the window under both panes while split,
so the 6px gap is the chrome renderer's — draggable (rAF-throttled IPC,
sender-resolved, clamped in main) and a focusable ARIA separator, with
no extra renderer.
- Entry points: NavigationMode.NewPanel ("new-panel", doc 37) — Alt+click
via navModeFromEvent, and an "Open to the side" row in the shared object
menu; registry shortcuts shell/split.{open,focus-other,swap,
divider-left,divider-right}, handled in main against the source window
and falling through to the app when the window isn't split.
- The intents service now passes the broker-verified sender id to the bus
so placement modes can find the window an open came from.
- Every-renderer paths (vault-lock mask, broadcasters, windowsFor,
running set, focus-existing) cover the side pane via panes().
- Browser (self-tabbing) windows refuse a split; addTabToContainer never
puts another app's launch into a container's strip.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ma + model settings (Agent-14) Settings → AI can now add, edit and remove named OpenAI-compatible endpoints (name, base URL, default model, optional key in the credential store), with an OpenRouter preset, and edit the Ollama endpoint + model (env stays the default). Each endpoint registers as a routable `custom-<hex>` provider and is re-applied to the live registry on every change and vault open. - Egress rides the network broker like every provider (SSRF floor, DNS pinning, proxy, audit). allowPrivate is derived from the URL: http only for loopback/LAN hosts; public endpoints need https and a key. - GLM, Mistral and Solheim are now routable defaults (the picker offered them but the store silently dropped them). - The broker validates pinned model ids (length cap, no control chars), requires an ai.provider:<id> cap for a pinned custom endpoint, and gains a ledger-filtered `ai.providers` listing for app model pickers. - Budgets: custom endpoints meter at FALLBACK_CLOUD_RATE; a provider that reports no usage is metered by estimate, never as zero tokens. - The OpenAI provider scrubs the key from echoed error bodies. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…picker (Agent-14) The conversation settings popover lists providers from the shell's `ai.providers()` (so custom endpoints show by name) and adds a free-form model field with the provider's known models as suggestions. The model persists on the conversation, clears on a provider change, and only a sane id is ever sent. Manifest gains ai.provider:mistral (was missing) and ai.provider:* for custom endpoints. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…s-1) Header search toggle + CmdOrCtrl+F / CmdOrCtrl+Shift+F (Escape closes) open a search strip over the transcript: This channel / All channels scope, newest- first results with channel, author, and date+time. Picking a result switches channel, scrolls the message into view, and flashes it. The scan runs over the live snapshot the app already holds (no new search capability; Message/v1 bodies aren't channel-indexed by the vault search), bounded at 50 results and a 2-character minimum query. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Files reads as a collection: a Filter toggle in the toolbar opens a Kind / Size / Modified / Extension row (shared SelectMenu; the Modified axis rides the SDK's live-rolling RelativeDateRange), composing with sort, group-by and search. A vault-wide Duplicates place in the sidebar groups File rows by their stored content hash, showing only groups of two or more, ordered by reclaimable bytes; filters apply before grouping. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- sdk-types: PropertyKindPreset.RichText (in KIND_PRESET_ORDER + defaults,
presetOf), RichTextValue { text, state? } + richTextPlain projection,
CellPresentation enum + optional CellProps.presentation.
- SDK RichTextCell (RichText::Block): one-line plain projection in a grid
with a popover editor; full in-place editor in an inspector
(properties panel passes Expanded); persists once per editing session,
blank clears; plain-textarea fallback when no editor is injected.
- richTextEditor seam on PropertiesProvider (the SDK can't import the
editor); editor package ships RichTextPropertyEditor over CompactEditor,
which gains initialState / initialText seeding.
- Inline add-property form: Text > Rich text format; Settings > Data badge,
icon, tint; Database wires the editor seam + Expanded inspector cells and
bridges rich values; Notes/Database label catalogs (6 locales each).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The block action menu's Turn into section gains Page. The selected blocks are snapshotted with the block-clipboard serializer (list items keep their list, block backgrounds ride along), the new Note is created and the snapshot planted into its Y.Doc through the renderer resolver, and only then are the blocks swapped for a page link — the same PageRefNode /subpage inserts — in ONE editor update, so a single undo restores the blocks and drops the link together (pinned at the Yjs UndoManager level). The first block's text becomes the title. A failed create or plant leaves the document untouched; if the blocks were edited while the page was written the swap is skipped so the newer text is never discarded; a selection containing the page title is refused. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ty query (9.22.7) An empty launcher query now leads with the user's saved searches and the objects they opened most recently, above the app list. A typed query ends with a 'save this search' row; saved searches apply on click and carry a remove button. Every entity row shows how many objects link to it. - LauncherMemory keeps both lists in settings.db (per device, encrypted at rest) under a reserved scope no app id can claim; recents cap at 12, saved searches at 20. - IntentsBus reports every delivered entity open through onEntityOpened, which is how recents fill; a failing observer never fails the open. - EntitiesRepository.countBacklinks is one grouped query per 64-id chunk, attached to search:query and search:recents results (no N+1). - SearchIndexer.lookup resolves recent ids to titles in one query. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…l unread badges (Chats-2) Read watermarks move from the device-local storage.kv key onto an app-owned per-user vault entity (io.brainstorm.chat/UserState/v1, keyed by the member's sovereign pubkey) so they ride the owner's device sync. The SDK has no synced kv (storage.kv and settings are both device-local by design). Duplicate rows from two devices that boot before syncing merge by per-channel max; writes target the lowest-id row and are debounced. The old kv key is the one-time migration source and is deleted after the first synced write; a host without a roster keeps the device-local path. Adds Mark as read / Mark all as read (channel right-click menu, header menu, Shift+Escape) and a CountBadge per unread channel. UserState is registered as a system entity type so type-enumerating surfaces treat it as plumbing. New capability: entities.write:io.brainstorm.chat/UserState/v1. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…(9.8.17, Files half) New @brainstorm-os/sdk/os-file-drop subpath: isOsFileDrag + a depth-counted OsFileDragTracker, bindOsFileDrop (DOM) / useOsFileDrop (React) drop zones, importDroppedOsFiles (files.import bytes variant -> File/v1-ready metadata, per-file failures reported, never aborting the batch) and importOsFilesAsEntities (adds entities.create for surfaces without a local tree — Database's collection views are the intended second consumer). mimeFromName moves from Files into the module. Files' content list and drop-upload path now ride it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…(Chats-3) The composer gains an expand/collapse control (CmdOrCtrl+Shift+E, chat.toggle-composer) that grows the CompactEditor to a drafting height. "Show full dates and times" in the header menu swaps Today/Yesterday day labels and bare clock times for full dates; the preference is stored on the per-user UserState row so it follows the user (device-local kv when the host has no roster). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ections are picked (9.12.27) - Database stage header: the List description is an inline edit field (Enter/blur commit, Escape reverts via the shortcut layer), revealed with an 'Add a description…' placeholder on header hover / focus; only entity-backed Lists are editable (vault-derived lists regenerate). - list-crud setListDescription (trimmed, no-op when unchanged). - SDK menus: ContextMenuItem/AnchoredMenuItem.description renders as muted, truncated caption text with the full text as the hover title (fits the fixed virtualized row height). - SDK cross-app 'Add to collection' picker + Database's own picker show each Collection's description; sidebar list rows carry it as tooltip + aria-description. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Settings -> Spellcheck dictionary gains a Spelling languages picker: the shared MultiSelectMenu over the platform's available hunspell dictionaries, plus Use system languages to go back to the OS order. The choice is validated main-side (an ordered, de-duplicated subset of what Chromium reports, max 8), persisted per device at <userData>/spellcheck-languages.json (dictionaries are a property of the machine, not the vault), loaded at boot and re-applied live to every spellcheck-enabled session. When nothing chosen is available the OS order still applies; macOS (no list exposed) keeps the auto-detect note. The custom dictionary is untouched. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The inspector's editable view gains a Caption row (a first-class File property, riding the grid as a host-owned extraRow with its own persister), a backlinks section listing every live object that links to the file (opens in a new tab, Shift for a new window) and a Move to Bin action over the soft-delete entities.delete path. Locked files refuse all three: the caption cell renders read-only, the Bin button is disabled with a hint, and both persisters early-return. A binned file leaves the gallery with the cursor on whatever slid into its slot. The backlinks walk is extracted to @brainstorm-os/sdk/backlinks (Preview is consumer two after Journal, which migrates onto it and folds its two near-identical link panels into one). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Settings > Backup & migration gains 'Export as Markdown': pick a folder and the vault is written as a dated folder tree, one .md per object grouped by type, YAML front matter for properties and timestamps, attachments copied into assets/. - @brainstorm-os/editor serializedStateFromDoc reads a universal-body Y.Doc back into a serialized tree by walking the @lexical/yjs encoding directly, so bodies with node types the shell has no class for still export. - Mentions, entity links and relation properties become relative links to the exported files; a reference to an object that is not exported (deleted, plumbing) becomes its plain label. No entity id reaches a file. - Streamed one object at a time with a yield every 25; abort or failure removes the partial folder; a same-day re-export never overwrites. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ts-4)
Pins: Pin / Unpin message from a per-message menu (hover ⋯ or right-click);
the pin list is stored on the channel row (Channel/v1.pinned, bounded at 50,
decoded defensively) so every member sees the same board. A header Pin button
lists the channel's pins newest first and jumps to the chosen message; it is
disabled with a reason when no channel is open.
Typing: the shared presence layer gains an optional, closed-set
PresenceActivity (Typing) on the payload — usePresence(..., { activity }) and
peerFromState drops unknown values. Chat publishes it at the first keystroke
of a burst and clears it after 4s idle, on send, and on channel switch (two
publishes per burst); typing is never written to an entity. Other members'
typing renders above the composer (aria-live).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…or emphasis, always-visible scrollbars (12.18) Settings → Appearance → Visibility adds three opt-in preferences painted over every theme. Higher contrast is an inline token-override layer derived from the theme itself (tokens higherContrastOverrides — borders/dividers/muted labels/ active fill each step up, never weaker); cursor emphasis and scrollbars are root data-attribute rules over the new color.emphasis.* tokens (every theme). Persisted per vault in the dashboard doc, applied by ThemeProvider in the shell and by the app preload (boot arg --brainstorm-display + app:display-changed broadcast) so every app renderer follows. Tests: per-theme override existence, 12.16/12.17 contrast lint under overrides, 3:1 strong border, AA under the emphasis selection wash, store/IPC/broadcast/launch/UI round-trips. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ponent Two 9.12.25 pentest holes (2026-10-19) in the `inverseOf` gate: - Concurrent `setProperty` / `removeProperty` calls raced the gate: each snapshotted the catalog, awaited `canWriteType` / `backfillInverse`, then wrote, so two interleaved calls each passed against a catalog missing the other's def (an app holding only `entities.write:Book` could pair onto a partner and widen it to Person in the gap). Catalog mutators are now serialised per store on a promise chain; the catalog is read under the lock. Reads stay unserialised. - `pairTargetTypes` collected the key's partners but not the partners' partners, so a third def naming one half of an existing pair was gated without the other half's types. The walk now closes transitively over `inverseOf` in both directions, bounded by a visited set. Adds `PropertiesMethod` for the handler's method discriminators. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
A menu is opened FROM the picker hosts (the Links-to / Two-way-with selects inside the property form), yet .fm-menu was pinned at 130 and the dimmer at 50 while the picker-host backdrop sits at 9000: the menu painted under the form and every click landed on the backdrop, which cancelled it. The stack vars are now literal top-of-stack values (dimmer 9100, menu 9101), .fm-dimmer / .fm-menu take their z from the vars so the ordering holds whichever sheet loads last, and menus.test.tsx fails if any SDK layer is ever declared above the dimmer. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…airing instead of dropping them findReusablePropertyDef matched on name + type alone, so naming a new two-way relation after an existing property reused that def and threw the Links-to target and the paired property away behind a 'reused' toast. resolvePropertyReuse now decides per draft: Reuse when the existing def already expresses the same settings, Adopt (write the target / pairing onto the existing def, re-pointing a new partner at its key) when it lacks them, and Distinct (mint the draft as its own def) when the existing def is already paired elsewhere. Both the column and the collection constructors follow the decision and the toast names which happened, in all six packs. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…and selection The pinned cell mirrored the row's hover / selected fill with the bare --hover and --accent-soft tints, both translucent (an accent over transparent, rgba(…, 0.06–0.16) in most themes), so a sticky cell let the cells scrolling beneath it show through — the selection appeared to slide over the Name column. Each mirrored face now layers its tint over the opaque page colour, and grid-view.test.ts fails if a pinned rule ever paints a tint bare again. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…itle The header is a flex row whose title had no flex-grow and no min-width, so a long title's basis pushed the whole row into shrink and the "3 of 4" counter folded onto three lines. The title is now the single flexible, truncating item (flex: 1 1 auto; min-width: 0); the nav slot and the counter are flex-shrink: 0 with white-space: nowrap. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…log record has no prototype A def keyed __proto__ landed on the snapshot's prototype instead of in it: invisible to Object.values and the dashboard, yet persisted in the Yjs doc. No capability bypass, but a catalog an app can hide things in is wrong. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…te (9.16.14) Pentest 2026-10-19 (low): every membership writer checked `isListLocked` up front, then awaited (entity creates, the 400 ms persist debounce) and committed through `saveUserList` regardless — a List locked by another surface in that window still took the members write. At the commit: - `saveUserList` / `deleteUserList` re-read the row and refuse a locked one (`SaveListOutcome` / `DeleteListOutcome.RefusedLocked`, carrying the persisted List). `reconcileUserLists` then drops the stale local List for the persisted row (re-adding a refused delete on its fallback Grid), re-renders, and flashes `status.listLocked`, so the UI stops showing a change that never landed. The kv migration honours the outcome too. - `createEmbeddedRow` re-reads the List through the BP graph after the create; locked (or unreadable — fail closed) means no members write, the row stays an unpinned orphan, and the error names it. - Board drop and "+ New" re-check the live lock (`liveListLocked`) right before `schedulePersist`, after their awaits. The known gap — rename, description, icon and delete of a locked List were ungated: - The pure CRUD returns a locked List untouched; the app flashes the refusal, the stage title / description are not editable while locked, and the sidebar Rename / Delete rows and the header ⋯ Remove render disabled with a new `list.menu.lockedHint` (all 6 packs). Tests: persistence refusal + outcomes, locked CRUD no-ops, embedded create race + fail-closed re-read. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…the grid's fields, segment padding, shared input face (a) csvToEntityImport wrote typed values under header keys but never registered a def, so an imported column had no Change type and no number unit — both read the catalog. propertyDefsForCsvImport mints one def per property column (keyed by the header the values live under, typed as inferred, Date at day granularity) and the import persists them before the list is created; keys the catalog already holds are left alone. (b) the plain-number formatter already appends def.unit; a 'people' unit rendered as a bare '3' only because the imported column had no def to carry it — (a) is the fix, pinned by a test through formatScalar. (c) createView started every view with columns: [] — a grid shows just Name, a gallery shows no fields at all. seedColumnsForNewView copies the visible columns of the view the user is on (else the list's first view) onto the new one. (d) .db-popover__segment had 2px of padding all round; now the row's space-1 / space-2 like the calendar segments. (e) the property form's name and paired-name inputs hand-rolled their own face (one unstyled entirely); both are .bs-input now and the private face is gone. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…st rename/delete)
# Conflicts: # apps/database/src/logic/list-crud.test.ts
The hourly retention pass (10.16) ran as one synchronous block: a vault-wide scan, the whole plan, one delete transaction over every dropped row, and an O(affected × rows) keep-set rescan per rewritten entity. At the byte cap on a 50k-row / 5k-entity vault that was a single 0.8–4.5 s block (3.7–18 s at 100k / 10k); the event loop never turned once. - `pruneLocked` groups rows by entity ONCE on the way in; each rewritten entity's keep set is read off its group (linear, no rescans). - The scan is paged by rowid (`RETENTION_SCAN_PAGE`), deletes go in `RETENTION_DELETE_CHUNK`-row transactions walked in scan order (2.5× cheaper per chunk than plan order), and the pass breathes (`yieldToEventLoop`) between pages, plan chunks, delete chunks and per-entity rewrites. A pass whose history is disposed mid-way stops instead of touching a closed vault. - `planRetention` is now driven off a generator that pauses every `PLAN_CHUNK` candidates, including inside a chunked merge sort, so `planRetentionYielding` never holds the thread for a whole vault; the sync `planRetention` drives the same steps and is byte-identical in result. Measured (Bun, in-process, 50k/5k and 100k/10k at the cap): wall 821 → 463 ms and 3666 → 1290 ms; longest synchronous block = whole pass → 7.6 / 12.4 ms; every scan page, plan step and per-entity rewrite under 5 ms; the residual tail is SQLite commit/checkpoint I/O inside 1–2 of the delete chunks. Tests: a 20k-row / 2k-entity golden (sha256 of the drop set captured from the original single-pass planner) for both drivers, the yield count, and a counting-repo test that every delete transaction holds ≤ 250 rows, one transaction per rewritten entity, and the pass pauses after every step. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
… through removeTestDir Both new suites removed their temp vault with a raw rm; on the Windows runner that is EBUSY while SQLite still holds the handle, so all 34 tests failed on the Verify leg. Every sibling suite already goes through the Windows-tolerant helper. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
After importing files, List and Icons view showed each File row as
"H.. pdf Today" — the name a sliver, the Kind cell in the name's lane.
Bookmark and Event rows showed the same.
Cause: `.content-row__glyph:empty { display: none }` removed the glyph host
from the row grid for any entity without its own icon, so the NAME became
the first grid item and sized to the icon track while the Kind cell took the
`1fr` lane. The `auto` track masked it (the name took its max-content width,
misaligned but visible) until 2445b8b pinned the track to a fixed px per
folder; 9.8.16 (567d22a) did not touch it.
Fix the class, not the instance: one shared `RowGlyph` replaces `TileMedia`
and the two list-mode branches, and is never empty — image thumb, own icon,
folder glyph, extension chip (tiles) / file-kind glyph (lists), opener-app
squircle, generic entity glyph. The track is fixed in the stylesheet (20px,
never `auto`) and `listIconTrack` no longer collapses to 0; the `:empty`
collapse is gone.
Tests: `row-glyph.test.tsx` renders every row kind in all four view modes
and asserts a non-empty glyph host; `styles-row-grid.test.ts` asserts the
first grid track is fixed-width and no `:empty` collapse exists.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The context panel showed "Modified —" for a freshly imported file while Files showed "Today" for the same row (9.20.15, release shot preview-context-panel.png). Cause: `entityToPreviewFile` read only `properties.updatedAt`, a property a `File/v1` row from Files never carries — its recency lives on the entity envelope, which is what Files' Modified column and Preview's own sidebar ordering (`previewFilesFromEntities`) already read. The Files intent push (`app.tsx`) was already correct; only Preview's self-resolve path lagged. Fix: resolve `entity.updatedAt` first, then the property, the same way Files does. Tests: +3 on `entityToPreviewFile` — envelope only, envelope over a stale property copy, property when the envelope has none. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Searching "coffee" in the shared emoji picker returned "No matches" (release shot fail-icon-recents.png). Cause: the search index was name + slug from `unicode-emoji-json`, which carries only the official CLDR names — ☕ is "hot beverage", ✅ "check mark button", ❤️ "red heart". None of the common names people type are in it. The 8.11 Recent row (5b0e40a) is not involved: search replaces the sections with the search results and never touched the matcher. Fix at the data level: `emoji-keywords.ts` — a curated slug → aliases map (GitHub/Slack-style shortcodes plus plain words for the common emoji). `searchEmojis` matches aliases alongside name and slug; the `:shortcode:` resolver and typeahead accept an alias too (`:coffee:` → ☕), with a real slug always winning over an alias. Tests: `emoji-keywords.test.ts` — coffee, heart, check, rocket and fire each return a hit that includes the expected emoji; every alias key is a real dataset slug (fails on a stale one); alias resolves as a shortcode; a real slug still resolves first. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
… document Today showed two entries and the quick capture landed in the wrong one. The editor autofocuses on open, which fires the placeholder create before the first vault snapshot arrives, so `ensureEntry` saw an empty day and minted `journal-<key>` beside the seeded welcome entry titled with the same date. The list then resolved the day by snapshot order (last row wins), so which entry the editor, the capture and the word count targeted was a race. - `logic/day-entry.ts`: one deterministic rule (earliest created, then id) for the list index, and `ensureDayEntry`, which asks the vault for the day's rows before minting — a day with a row anywhere in the vault reuses it (the welcome entry when it is today's). A seeded id now also opens on its day. - `logic/capture-into-day.ts`: the capture resolves the day first, then appends through the live editor mounted on THAT entry; the editor bridge records the entry it is mounted on and refuses a sibling document, so the `body` snippet and word count are mirrored onto the row the list shows. Tests: day-entry (pick / rows / ensure / open-by-id), capture-into-day, index determinism, bridge note-id guard. apps/journal 244/244. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…utside A side pane only ever left the split through the container's own paths (Cmd+W in the pane, closeSidePane, dispose). A renderer closed from outside — DevTools, another webContents.close(), the harness closing a page — stayed registered as the side pane, so the split stayed open and the primary pane sat at pane width (717 px on a 1440 window) for the rest of the window's life, including after a park/unpark. Every pane now listens for its webContents `destroyed` and leaves the container the same way a user close would; the container's own teardown detaches the listener before it closes the renderer. Pane geometry never reached the remembered window size: WindowIndex reads the BaseWindow's bounds and launch-setup opens every window at the fixed default. Tests pin both invariants: un-splitting hands the full body back to the surviving pane, a divider drag or step never calls setBounds on the OS window, the tracker keeps the window's bounds across a pane layout, and a destroyed side pane / strip tab leaves the window. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…t be undone The Move-to-Bin prompt read "This can't be undone here" directly above the "Don't ask again" checkbox — a checkbox the shared confirm memory offers only because moving to the Bin IS reversible. The body now says the item goes to the Bin and can be restored from there, in all six packs (en, de, fr, it, es, pt). Code's file-delete prompt carried the same sentence and takes the same copy. The irreversible kinds keep their wording. A per-locale test on each app's shipped text pins the contract: the Move-to-Bin body never claims irreversibility and always offers a restore. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…'s lineage walk Renaming a tab (double-click, F2, the options menu) and adding a tab threw `RangeError: Invalid array length` and reset the editor, so the rename input never appeared. Chrome reports a focused `<input>` or a clicked strip control as a document selection at its parent + child index, a DOM point inside the tabs block but outside the panes slot. Lexical 0.21's `ElementDOMSlot.resolveChildIndex` walks the lineage of such a point with `indexPath`, whose loops never advance (`node = child.parentNode`, `sibling = node.previousSibling`), so it pushes until V8 refuses the array; the `$beginUpdate` catch then rebuilds the DOM and the strip loses its state. `TabsNode.getDOMSlot` now resolves any point outside the panes slot into the pane being shown (its `hidden` attribute is right there), so `$selectionPane` never switches tabs under a rename and Lexical's own descent lands the caret in the visible pane. Covered by two jsdom tests that place the selection on the rename input and on the add button under a lowered `Array.prototype.push` ceiling, which reproduces the exact RangeError before the fix. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…pt and the composer "Maya Chen is typing…" was absolutely positioned above the composer, so once the transcript filled the column it painted across the last message. The row is now in flow with a reserved 20px height, so the label never overlaps content and the list does not jump when it appears or clears. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…r people
The agent's "Changes by the agent" panel listed `values: {}`,
`createdAt: 1792445200119` and `updatedAt: …` as if the agent had chosen them.
`AGENT_CHANGE_HIDDEN_KEYS` (the reserved provenance keys plus createdAt,
updatedAt, values) now drops those rows in the shell's `buildRunChanges` and,
defensively, in `<RunChanges>`; an empty object or array previews as nothing,
so a row that says nothing is omitted on both sides. `humanFieldChanges` /
`formatChangePreview` format an epoch stamp on a date-shaped key (`dueAt`,
`publishedDate`) through the shared date formatters, honouring the host's
`FormatContext`, which the Agent app passes from `useFormatContext`.
Co-Authored-By: Claude Fable 5.1 <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.
Cuts v0.15.0: the 48-rung market-demand bucket (plan row 0.15.0, harness
docs/apps/market-demand-2026-10.md), the hardening round that followed four review passes, the pipeline debt from the 0.14.0 cut, and the release entry. 156 commits, one integration branch per the batching rule.Full suite on the final head c9c63f0: 1,881 files / 19,133 tests green;
bun run typecheck(packages + apps) andbun run lint(biome + every ratchet) clean.1. Database (
9.12.24–9.12.35)Contextual inline views (
{$host:true}), two-way relations (PropertyDef.inverseOf, mirrored on create/update/delete, far ends cap- and lock-checked before any write), rich-text property, collection descriptions, board swimlanes + group controls, column freedom, chart view + dashboard widget, gallery cover/field settings, bulk edit with one-step undo, change a property's type with a preview + number units, next/previous object, pin a view.2. Notes (
B11.21–B11.31)Insert menu, editable synced blocks, block + table-cell backgrounds (tokens in all 14 themes),
/task, turn block into page, side outline panel, regex find and replace (bounded), media from the vault + inline image width, tabs block, list guides + outliner keys, spell-check language picker.3. Shell (
6.14,6.15,7.15–7.17,8.11,9.22.7,10.16,12.18,IE-13,IE-14)Split view (per-pane renderer identity, forged pane ids rejected through the Broker), "don't ask again" on reversible prompts only, auto-hide sidebars, close to tray, startup page, recent emoji/icons, search when empty + backlink counts, per-object version history (entities.db v13,
ydoc-framecodec, retention policy,historybroker service), visibility preferences, whole-vault Markdown export, scheduled backups with restore points.4. Agent (
Agent-14–Agent-16)Provider + model settings with every AI egress through the network-broker transport; run diff and whole-run undo (
agent_writes, entities.db v11); agent authorship projected read-only and unforgeable on every write path.5. Chat, collaboration, the rest (
Chats-1–4,Collab-C7,9.14.17,9.16.14,9.8.16/17,9.20.15,9.17.24)Message search, per-user read state, composer + time prefs, pins + typing; invite links that only pre-fill the share dialog, Viewer default, last-editor record; three-state task checkbox; Journal quick capture into a collection; Files filters + duplicates + OS file drop (
@brainstorm-os/sdk/os-file-drop); Preview context panel (@brainstorm-os/sdk/backlinks); Whiteboard viewport restore.6. Hardening (
fix/0.15.0-harden-*,fix/0.15.0-pair-gate-race,fix/0.15.0-lock-at-commit,fix/0.15.0-database-friction,fix/0.15.0-history-retention)SH-ydoc-gate); the AI service re-checks the ledger on every call (SH-ai-gate).__proto__is never a catalog key and the catalog record has no prototype.9.12.25:setProperty/removePropertyserialised per store; the pair gate closes over the whole pair component (chains, cycles, missing partners).9.16.14: a List lock holds at the commit (saveUserList/deleteUserListre-read and refuse, embedded create-row re-reads before pinning); rename/description/icon/delete of a locked List refused with a disabled, hinted menu row.10.16retention is linear and chunked: 250-row delete transactions, yields after every page/plan chunk/relocate; longest main-thread block 7.6 ms at 50k rows (was the whole 0.8–4.5 s pass).resolvePropertyReuse: Reuse/Adopt/Distinct instead of silently dropping a pairing), F-509 (pinned column opaque), F-510 (inspector nav counter on one line), F-511 (CSV columns get catalog defs; new views seed visible columns; segment padding; sharedbs-inputface).fix/0.15.0-shots-*, F-512..F-520): Files list rows always render a glyph on a fixed icon track (names were invisible since the shared icon track of 2026-08-08); the Notes tabs block no longer trips Lexical 0.21's brokenresolveChildIndexloop on rename/add (TabsNode.getDOMSlotoverride, droppable on a Lexical bump); Chat's typing indicator is an in-flow row; Preview reads Modified from the entity envelope; Journal resolves a day to ONE entry (the welcome seed) and captures through its live document; a split pane destroyed from outside un-splits its container; the emoji picker searches keyword aliases; Move-to-Bin prompts say the item can be restored; the Agent run-changes panel hides host-owned keys and formats dates.7. Pipeline + release (
SH-42)latestbroke the 0.14.0 Windows build with zero code changes).packages/shell/package.json+ root; the in-app What's New entry (58 blocks, validated by the changelog tests).BRAINSTORM_FEEDBACK_ENDPOINTstill needs an org secret (owner-only; the collector deploy is deferred).release.ymlworkflow_dispatchdry-run is dispatched on this branch.Gates (recorded in harness
docs/_review/evaluations.jsonl, 2026-10-19)security-review + pentest pass:
10.16,12.18,6.14,9.12.24,9.12.25,9.12.26,9.12.32,9.20.15,9.22.7,9.8.17,Agent-14,Agent-15,Agent-16,B11.23,B11.24,B11.31,Chats-2,Collab-C7,IE-13,IE-14,SH-ai-gate,SH-ydoc-gate; security-review pass + pentest pass9.16.14;B11.27security-review pass;10.16perf pass after the retention fix.Residue carried (on the plan row)
Shell
entities.update/deletedo not enforceproperties.lockedserver-side (pre-existing advisory lock design shared by six apps; alockRefusesWritecheck there would close the one-IPC-gap TOCTOU too); a refused List delete still bins its views; split restore across restarts + Browser exclusion; Database ignores 7.15 auto-hide; editor/main-process strings untranslated; filter embeds run one full-vault scan per load; hard app-lock does not drain pending history; non-agentBeforeRestorecheckpoints have no per-app cap; backups unsigned; delete does not report skipped far ends; retention's 1–2 WAL-commit spikes per hourly pass at 10–15 ms.🤖 Generated with Claude Code