From fa9d622673f8977deb9c1f1172396b9fa7dad01a Mon Sep 17 00:00:00 2001 From: shikokuchuo <53399081+shikokuchuo@users.noreply.github.com> Date: Tue, 27 Oct 2026 15:54:22 +0000 Subject: [PATCH 1/2] Measure wide-dep useEffect cost; no fix warranted (bd-wfhd0dg5) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Phase 4 of the useEffect audit (plan: claude-notes/plans/2026-10-24-hub-client-useeffect-refactor.md). Adds src/utils/perfCounters.ts — a QUARTO_PERF_STATS/localStorage/?perf=1 gated counter helper — and instruments the five fileContents-wide effects plus the intelligence refresh. Throwaway Node drivers (tsx + jsdom, real hooks, real Automerge doc, real WASM vfs_add_file) measured per-keystroke cost at geometric scales: - Reconcile effect: O(n) with small constant, <=0.27 ms at <=82 KiB docs; the redundant on-main vfs write is <=0.3 ms even at 327 KiB — not worth the branch-transition-tracking risk to skip. - useProjectSearch: corpus rebuild <=30 us at 400 files; the residual ~0.8 ms is intrinsic MiniSearch reindexing of the one changed file. - Trivial-body effects (route-nav, replay-VFS, preview schedulers, intelligence refresh): 0.036 ms/keystroke for five incl. React commit. No dep is narrowed and no work is moved; the counters stay in the tree. useSectionThumbnails was dead code (no caller; OutlinePanel imported only its ThumbnailMap type) — deleted, closing the debounce item. The refreshIntelligence effect is already debounced at 300 ms inside useIntelligence; no fold-in needed. --- .braid/snapshot.jsonl | 31 +++- ...026-10-24-hub-client-useeffect-refactor.md | 116 +++++++++++-- hub-client/src/components/Editor.tsx | 8 + hub-client/src/components/OutlinePanel.tsx | 2 +- hub-client/src/components/render/Preview.tsx | 3 + .../src/components/render/ReactPreview.tsx | 3 + hub-client/src/hooks/useAutomergeSync.ts | 28 ++- hub-client/src/hooks/useSectionThumbnails.ts | 134 -------------- .../src/services/search/useProjectSearch.ts | 15 ++ hub-client/src/utils/perfCounters.test.ts | 104 +++++++++++ hub-client/src/utils/perfCounters.ts | 164 ++++++++++++++++++ 11 files changed, 446 insertions(+), 162 deletions(-) delete mode 100644 hub-client/src/hooks/useSectionThumbnails.ts create mode 100644 hub-client/src/utils/perfCounters.test.ts create mode 100644 hub-client/src/utils/perfCounters.ts diff --git a/.braid/snapshot.jsonl b/.braid/snapshot.jsonl index 25301c52f0..7a29f38bc3 100644 --- a/.braid/snapshot.jsonl +++ b/.braid/snapshot.jsonl @@ -5,6 +5,7 @@ {"id":"bd-038tnyqy","title":"Fix hub-client E2E: textarea-surface specs broke when richText defaulted ON","description":"After enabling richText by default in hub-client (bd-j1nto6eq), ~25 q2-preview editing e2e specs time out: they click a block and waitFor('textarea'), but the rich editor (.ProseMirror) now opens instead. Fix at the single chokepoint bootstrapProjectSet (every editing spec's openFile calls it): pin richText:false as the e2e textarea baseline via an addInitScript that merges richText:false into the seeded prefs only when a spec hasn't set it explicitly (so future rich-text specs opt in with richText:true). NOT mine: q2-debug-render-components, share-link-project-set (poll timeout), render-components-comment/kanban (Path not found / EDITOR_NO_PREVIEW) — pre-existing infra/flakiness.","status":"closed","priority":1,"issue_type":"bug","created_at":"2026-06-24T20:03:26.079705Z","created_by":"Carlos Scheidegger","updated_at":"2026-06-24T20:07:04.210606Z","closed_at":"2026-06-24T20:07:04.210606Z","close_reason":"Pinned richText OFF as e2e textarea baseline at the bootstrapProjectSet chokepoint; affected editing specs pass locally. Pushed b91d7277.","dependencies":{"bd-j1nto6eq:discovered-from":{"depends_on_id":"bd-j1nto6eq","type":"discovered-from","created_at":"2026-06-24T20:03:26.079705Z","created_by":"Carlos Scheidegger"}},"comments":{"c-yk1jbi3g":{"id":"c-yk1jbi3g","author":"Carlos Scheidegger","created_at":"2026-06-24T20:07:03.217407Z","text":"Fixed in b91d7277 (pushed to PR #335 branch). Single-point fix in bootstrapProjectSet: addInitScript merges richText:false into seeded prefs unless a spec sets it explicitly. Verified locally (harness self-starts hub + serves build): inline-edit + delete-by-emptying = 9 passed (both merge paths); block-nav-p2-5b + breadcrumb-geometry + expand-on-edit + self-heal-on-write = 25 passed / 1 skipped. q2-debug/share-link/render-components-comment+kanban failures are pre-existing infra (peer-connection / poll timeout / Path-not-found), not richText."}}} {"id":"bd-05yzq20u","title":"Project-root _metadata.yml is silently ignored (no layer, no warning)","description":"Verified 2026-08-19 (assessment claude-notes/research/2026-08-19-path-resolution-class-assessment.md §1, third variant): a _metadata.yml at the project root carrying format.html.include-in-header is applied to NO input (neither root-level nor subdirectory documents) and produces zero warnings. A subdirectory _metadata.yml IS honored for that directory's documents. Reported as a maybe-second-bug in GH #455's notes; confirmed real. Likely in directory_metadata_for_document (metadata_merge.rs dir_layer_entries) skipping the project root directory. Separate from the declaration-site resolution fix (bd-oejuizi9): even with correct path rebasing, the root layer never enters the merge.","status":"open","priority":2,"issue_type":"bug","created_at":"2026-08-19T15:11:53.101551Z","created_by":"Carlos Scheidegger","updated_at":"2026-08-19T15:11:53.101551Z","dependencies":{"bd-oejuizi9:discovered-from":{"depends_on_id":"bd-oejuizi9","type":"discovered-from","created_at":"2026-08-19T15:11:53.101551Z","created_by":"Carlos Scheidegger"}}} {"id":"bd-068k","title":"Phase 0: quarto-publish crate scaffold + provider trait","description":"Phase 0 of bd-t3ny. Build the quarto-publish crate skeleton: types, PublishProvider trait (prepare/commit/verify), PublishHost, PublishRenderer, ProviderRegistry, NativeHost, CLI argument validation, and wire into crates/quarto/src/commands/publish.rs (replacing NotImplemented stub).\n\nProvider impls in this phase: gh-pages registered with prepare/commit unimplemented!() — tests exercise registry lookup and CLI dispatch only.\n\nPlan: claude-notes/plans/2026-05-03-publish-command-and-gh-pages.md (Phase 0 section)","status":"closed","priority":1,"issue_type":"task","created_at":"2026-05-03T14:37:32.946678Z","created_by":"cscheid","updated_at":"2026-05-03T14:57:58.079046Z","dependencies":{"bd-t3ny:parent-child":{"depends_on_id":"bd-t3ny","type":"parent-child","created_at":"2026-05-03T14:37:32.946678Z","created_by":"cscheid"}}} +{"id":"bd-06s9sufx","title":"hub-client useEffect refactor Phase 2: useSyncExternalStore conversions","description":"Phase 2 of claude-notes/plans/2026-10-24-hub-client-useeffect-refactor.md: convert useDocBranches (tear fix, needs memoized branchService snapshots), usePreference, usePresence to useSyncExternalStore; add shared useNow/useOnlineStatus hooks and replace the duplicated ticker+online blocks in SyncStatusBadge and ConnectionStatusDialog; document intentional polling in DebugInspectorPanel.","status":"closed","priority":2,"issue_type":"task","created_at":"2026-10-27T11:14:45.176435Z","created_by":"shikokuchuo","updated_at":"2026-10-27T11:36:10.398986Z","closed_at":"2026-10-27T11:28:19.668342Z","close_reason":"Phase 2 implemented, tested and committed (5645dd547); awaiting push approval","comments":{"c-80a4hiah":{"id":"c-80a4hiah","author":"shikokuchuo","created_at":"2026-10-27T11:36:10.398986Z","text":"Rebased onto main (5fb0b709c) with Phase 1: 6a4a0d214 + 3d8286ffb, Phase 2 now fccb7c91d. Full test:ci green on the rebase (1720 unit, 225 integration, 434 WASM) after npm run build:wasm — the earlier 3 golden-parity failures were a stale local WASM, not source skew. npm run build green."},"c-xwbvbv31":{"id":"c-xwbvbv31","author":"shikokuchuo","created_at":"2026-10-27T11:28:15.881736Z","text":"Done as 5645dd547 on refactor/bd-06s9sufx-useeffect-phase2 (stacked on Phase 1 branch). All six Phase 2 items landed; unit (1718) + integration (225) legs green, npm run build green, eslint clean (one pre-existing usePresence warning). WASM leg has 3 golden-parity failures reproduced on the clean tree (pre-existing skew Phase 1 documented, unrelated). No changelog entry — the file's own rule excludes refactors. Not browser-smoked; not pushed."}}} {"id":"bd-07kbzn1d","title":"Bind hub websocket peer IDs with Transport::with_expected_peer_id","description":"samod 0.15 added Transport::with_expected_peer_id / accept_axum(socket, expected_peer_id): bind an accepted connection to an externally authenticated peer ID instead of trusting the claimed handshake identity. The hub currently passes None (bd-jvag5d4w migration) — safe today because nothing in the hub decides on a peer ID (the only consumer is a debug log line; the audit events key on the authenticated sub). But the moment a SharePolicy keys on PeerId — e.g. the ShareAccess::ReadOnly viewer/collaborator split upstream built this for — this binding becomes MANDATORY, and the client-chosen peer ID must be bound at upgrade time (derived from the session, or sent by the client and checked against it). Relevant to the bearer and cookie paths; UDS is local-trusted and unauthenticated. Depends on project-permissions design work.","status":"open","priority":2,"issue_type":"task","created_at":"2026-10-26T14:44:49.967865Z","created_by":"shikokuchuo","updated_at":"2026-10-26T14:44:49.967865Z","dependencies":{"bd-jvag5d4w:discovered-from":{"depends_on_id":"bd-jvag5d4w","type":"discovered-from","created_at":"2026-10-26T14:44:49.967865Z","created_by":"shikokuchuo"}}} {"id":"bd-09aja9gl","title":"Phase 4: iframe embed of /debug.html (openServerInspector) for live-vs-server comparison","description":"quartoDebug.openServerInspector(): inject iframe of debug.html seeded via existing #doc= hash with current index doc URL; separate server-connected Repo by design, companion to the in-context views. Plan: claude-notes/plans/2026-07-29-hub-client-in-context-debugging.md","status":"closed","priority":3,"issue_type":"task","created_at":"2026-07-30T17:23:35.751591Z","created_by":"Carlos Scheidegger","updated_at":"2026-07-30T18:18:35.804462Z","closed_at":"2026-07-30T18:18:35.804462Z","close_reason":"Implemented, tested (5 unit tests + debugApi wiring), verified on local-prod with both inspectors open simultaneously; embedded debugger connected with its own peer and showed the seeded index doc. Commits 9e65fcdc + changelog.","dependencies":{"bd-aim2gqis:parent-child":{"depends_on_id":"bd-aim2gqis","type":"parent-child","created_at":"2026-07-30T17:23:35.751591Z","created_by":"Carlos Scheidegger"}},"comments":{"c-njs2xwip":{"id":"c-njs2xwip","author":"Carlos Scheidegger","created_at":"2026-07-30T18:15:41.999337Z","text":"Implementation done: debugServerInspector.ts plain-DOM overlay embedding debug.html#doc=, wired as quartoDebug.openServerInspector/closeServerInspector with help() text; uninstall closes it. 5 unit tests + debugApi wiring tests; 823 hub-client unit tests green. Building local-prod for the manual verification pass."}}} {"id":"bd-0a37l74z","title":"Sidebar TOC has no font-family variable: doc-toc h2 forces font-family: inherit, so TOC always uses the body face","description":"The sidebar TOC (\"On this page\" + entries) always renders in the body typeface, even when a theme or _brand.yml sets a distinct headings face. There is no supported knob to change it.\n\n## Cause (ported Q1 behaviour, not a regression)\n\nresources/scss/bootstrap/_bootstrap-rules.scss (the block containing `font-family: inherit;`, around lines 1908,3007) has:\n\n .sidebar .toc-actions h2, .sidebar .quarto-code-links h2, .sidebar .quarto-other-links h2,\n .sidebar .quarto-alternate-notebooks h2, .sidebar .quarto-alternate-formats h2,\n .sidebar nav[role=\"doc-toc\"] > h2 {\n font-weight: 500; ... font-family: inherit; ...\n }\n\nThe TOC title is an h2, so Bootstrap's reboot gives it $headings-font-family; this rule then forces `inherit`, so it takes the body face from . The TOC entries are plain `ul li a` and inherit the body face too. Verified on a live page (EB Garamond body / Merriweather Sans headings via _brand.yml): computed font-family on nav#TOC, its h2, and its links is EB Garamond; main h2 is Merriweather Sans. Same rule exists verbatim in Quarto 1 (external-sources/quarto-cli/src/resources/formats/html/bootstrap/_bootstrap-rules.scss ~L1213-1225), so this is parity, and the default should not change.\n\nQuarto exposes $toc-font-size, $toc-color, $toc-active-border, $toc-inactive-border, $toc-tools-font-size (resources/scss/bootstrap/_bootstrap-variables.scss:51-56) but no font-family variable for the sidebar.\n\n## Proposed change\n\n1. Add `$toc-font-family: inherit !default;` (and possibly `$toc-font-weight: 500 !default;`) next to the other $toc-* variables.\n2. Use it in the rule above instead of the literal `inherit` (for the doc-toc h2), and set `font-family: $toc-font-family` on `.sidebar nav[role=\"doc-toc\"]` so entries follow it as well. Consider the same variable for the sibling other-links / code-links / alternate-formats h2s, or a separate $toc-tools-font-family.\n3. Default stays `inherit`, so existing output is byte-identical unless a theme sets the variable.\n4. Optional follow-up: decide whether brand typography.headings should feed it (probably not by default — Q1 intent is that the sidebar reads as UI chrome, not document headings — but a theme author can then write `$toc-font-family: $headings-font-family`).\n\n## User-side workaround (works today)\n\n /*-- scss:rules --*/\n .sidebar nav[role=\"doc-toc\"] {\n font-family: $headings-font-family;\n font-weight: $headings-font-weight;\n }\n\nFound 2026-09-08 while styling cscheid-net-2026 with _brand.yml.","status":"open","priority":3,"issue_type":"feature","created_at":"2026-09-08T16:54:00.549683Z","created_by":"Carlos Scheidegger","updated_at":"2026-09-08T16:54:00.549683Z","labels":["css","theming"]} @@ -16,7 +17,7 @@ {"id":"bd-0gsj","title":"Fix CRLF handling in tree-sitter-qmd grammar.js for pipe tables","description":"Tree-sitter grammar at crates/tree-sitter-qmd/tree-sitter-markdown/grammar.js parses pipe tables incorrectly when input uses CRLF line endings. With LF input, the pipe table closes at the blank line and following content becomes a separate block. With CRLF input, the table absorbs the following paragraph as a malformed body row. Bug propagates through pampa end-to-end: rendered Pandoc AST is structurally wrong for any Windows user with core.autocrlf=true (default) authoring a pipe table followed by a paragraph. Affects all output formats (HTML, PDF, Word) since Pandoc AST is upstream of every writer. Surfaced 2026-04-24 during PR 109 Windows verification as 5 failing pipe-table corpus tests; investigation in bd-ntnx classified it as a real grammar bug, not a corpus formatting issue. Full reproducer and observed parse trees in the design field. Cross-reference q2-windows-verify-tests memory note for related Windows CRLF failures.","design":"Reproducer. Two layers, both diagnostic. Layer 1 tests grammar.js directly. Layer 2 tests the full pampa pipeline that real users hit. Fixtures live in the tree-sitter-markdown directory so tree-sitter parse picks up the configured grammar without an init-config step.\n\nLayer 1 (grammar.js):\n\nprintf \"before\\r\\n\\r\\n| a | b |\\r\\n|---|---|\\r\\n| 1 | 2 |\\r\\n\\r\\nafter\\r\\n\" > crates/tree-sitter-qmd/tree-sitter-markdown/test-crlf.qmd\nprintf \"before\\n\\n| a | b |\\n|---|---|\\n| 1 | 2 |\\n\\nafter\\n\" > crates/tree-sitter-qmd/tree-sitter-markdown/test-lf.qmd\ncd crates/tree-sitter-qmd/tree-sitter-markdown\ntree-sitter parse test-crlf.qmd > test-crlf.parse\ntree-sitter parse test-lf.qmd > test-lf.parse\ndiff test-crlf.parse test-lf.parse\n\nLayer 2 (pampa end-to-end):\n\ncargo run --quiet -p pampa --bin pampa -- crates/tree-sitter-qmd/tree-sitter-markdown/test-crlf.qmd > pampa-crlf.out\ncargo run --quiet -p pampa --bin pampa -- crates/tree-sitter-qmd/tree-sitter-markdown/test-lf.qmd > pampa-lf.out\ndiff pampa-crlf.out pampa-lf.out\n\nObserved parse trees from Layer 1 on this branch (fix/windows-tree-sitter-crlf at 80f07171). Both trees agree on the prefix, so the divergence is the trailing block:\n\nLF (correct, table closes at the blank line):\n pipe_table [2, 0] - [5, 0]\n pipe_table_header [2, 0] - [2, 9] ...\n pipe_table_delimiter_row [3, 0] - [3, 9] ...\n pipe_table_row [4, 0] - [4, 9] ...\n pandoc_paragraph [6, 0] - [7, 0]\n pandoc_str [6, 0] - [6, 5]\n\nCRLF (buggy, table absorbs following paragraph):\n pipe_table [2, 0] - [7, 0]\n pipe_table_header [2, 0] - [2, 9] ...\n pipe_table_delimiter_row [3, 0] - [3, 9] ...\n pipe_table_row [4, 0] - [4, 9] ...\n pipe_table_row [6, 0] - [6, 5] <- SPURIOUS, should be a separate paragraph\n pipe_table_cell [6, 0] - [6, 5]\n pandoc_str [6, 0] - [6, 5]\n\nObserved Pandoc AST from Layer 2 (pampa default -t native), confirming the bug propagates past pampa input handling:\n\nLF: [Para [Str \"before\"], Table ... [body row [1,2]], Para [Str \"after\"]]\nCRLF: [Para [Str \"before\"], Table ... [body rows [1,2] AND [Plain \"after\"]]] <- no trailing Para\n\nConfirms pampa does NOT normalize line endings before feeding the grammar. End-user impact: any qmd document with CRLF endings, a pipe table, and following content produces structurally wrong output in every writer that consumes Pandoc AST.\n\nOpen design questions for the fix.\n\n1. Locate the bug in grammar.js. Likely candidates: newline character classes that match only \\n, or the external scanner (scanner.cc) handling of block boundary detection. Read both grammar.js and any external scanner before patching. Tree-sitter-markdown forks vary on this — confirm what our fork does versus what works.\n\n2. Upstream vs local fix. This grammar is forked from MDeiml/tree-sitter-markdown (or whichever upstream we track — confirm in tree-sitter.json or README). If upstream already fixes this, cherry-pick. If not, patch locally and consider an upstream PR.\n\n3. Test-corpus line-ending policy after the fix. Two viable strategies:\n a. Keep test/corpus/*.txt files materialized as CRLF on Windows checkout and rely on the fixed grammar to parse them identically. Tests then exercise CRLF on Windows and LF on Linux, which is useful coverage but creates a small risk that future grammar changes regress CRLF handling silently when only Linux CI runs.\n b. Pin test/corpus/*.txt to LF via .gitattributes (eol=lf for that subdirectory only) and add a separate Windows-only regression test that re-creates the fixtures with CRLF and asserts the parse trees match. More explicit, less coverage drift.\n\n The choice depends on whether we expect frequent grammar changes and whether ubuntu-only CI is sustainable. Recommend deciding before landing the grammar fix.\n\n4. Audit other CRLF patterns. The 5 failing pipe-table tests are the surface; there may be other constructs whose grammar paths assume \\n only. Grep grammar.js (and scanner.cc if present) for \\n usages and audit each one.\n\nReference. Investigation history and original framing in bd-ntnx (closed). Memory note q2-windows-verify-tests catalogues related Windows CRLF failures.","acceptance_criteria":"tree-sitter parse on matched CRLF and LF qmd inputs produces identical S-expression trees for pipe-table fixtures (Layer 1 reproducer diff is empty). pampa default native output is identical for the same matched inputs (Layer 2 reproducer diff is empty). All 5 originally-failing pipe-table tests under cd crates/tree-sitter-qmd/tree-sitter-markdown && tree-sitter test pass on Windows: Example 201 (GFM), Pipe table can precede content (two variants), Pipe table can follow content, Pipe table with blank-line caption. cargo xtask verify step 4 (tree-sitter test) passes on Windows. cargo nextest run --workspace and cargo xtask verify continue to pass on Linux CI (no regression). Decision recorded in commit message or PR description for upstream-vs-local fix and for test-corpus line-ending policy. Audit findings recorded for any other CRLF patterns discovered in grammar.js or scanner.cc. q2-windows-verify-tests memory note updated with PR link and resolution.","status":"closed","priority":1,"issue_type":"bug","created_at":"2026-04-27T14:26:53.718601600Z","created_by":"cderv","updated_at":"2026-05-04T13:45:00.482193500Z","closed_at":"2026-05-04T13:45:00.481818100Z","close_reason":"PR #139 merged 2026-04-30: fixed CRLF pipe-table line ending in scanner.c (typo fix at line 2259), added Rust regression test and xtask CRLF parity check","external_ref":"https://github.com/quarto-dev/q2/issues/138","labels":["tree-sitter","windows"],"dependencies":{"bd-ntnx:discovered-from":{"depends_on_id":"bd-ntnx","type":"discovered-from","created_at":"2026-04-27T14:26:53.718601600Z","created_by":"cderv"}},"comments":{"c-2vcbuybu":{"id":"c-2vcbuybu","author":"cderv","created_at":"2026-04-28T16:05:51Z","text":"Tomorrow's verification (next session pickup): (1) check PR #139 CI status — gh pr checks 139 --repo quarto-dev/q2; (2) if green, merge; (3) on merge, close bd-0gsj with PR link as the close reason; (4) flush and commit accumulated .beads/issues.jsonl from main repo (this session created bd-vxl8 and bd-picv, plus comments on bd-238o, bd-3pe8, bd-0gsj); (5) update q2-windows-verify-tests memory note status to fully resolved."},"c-swxf3f4l":{"id":"c-swxf3f4l","author":"cderv","created_at":"2026-04-29T16:25:17Z","text":"CRLF parity check landed in commit 8f61f587 on branch fix/windows-tree-sitter-crlf (PR #139). cargo xtask verify step 4 now re-runs tree-sitter test against a CRLF-converted corpus copy; 451/451 pass, regression-tested by reverting scanner.c (446/451, build fails). Awaiting PR merge to close."},"c-xjxgh7ir":{"id":"c-xjxgh7ir","author":"cderv","created_at":"2026-04-28T15:37:36Z","text":"PR #139 opened (https://github.com/quarto-dev/q2/pull/139). Layer 1 + Layer 2 reproducer diffs empty post-fix. tree-sitter test on Windows: 451/451 (was 446/451). Decision: local fix, not upstream — typo introduced on this fork in 79489c4e. Decision: no .gitattributes change for test corpus — fixed grammar parses LF and CRLF identically, so existing corpus tests both modes (LF on Linux, CRLF on Windows checkout). Audit: line 2259 was the only outlier; all other newline checks in scanner.c use paired '\\n' || '\\r'. Added Rust regression test pipe_table_crlf_matches_lf for cross-platform coverage."}}} {"id":"bd-0gwekaem","title":"nested_cell_mask doesn't cover inline expressions, so a displayed `{r} x` still executes","description":"The nested-cell mask (claude-notes/plans/2026-09-02-nested-cell-masking.md, \"Make q2 self-documenting again: don't execute a cell that is being displayed\") suppresses execution of an executable *fence opener* inside a markdown-displaying CodeBlock. It does not cover *inline* expressions, so an inline brace expression inside a display block still runs.\n\nWHY IT DIDN'T APPLY BEFORE\nThe mask's Scope section lists what is deliberately out (RawBlock, doubled-brace, {=html} openers, cells nested in cells). Inline expressions are not on that list because they were not reachable: q2 never evaluated the `{r} expr` spelling at all. bd-inline-r-brace-spelling-not-evaluated-lk9s3iwe made that spelling live, which is what opens this hole. The fix for that strand is correct and matches Quarto 1; this is the follow-on the mask needs to stay whole.\n\nMEASURED, q2 (with the inline fix) vs Quarto 1 99.9.9. Fixture: knitr document, `v <- \\\"EVALUATED\\\"`, then each form. EVALUATED means the escape failed.\n\n form q2 Q1\n plain prose executes executes (the feature)\n `` `{r} v` `` extra backticks executes executes\n `{{r}} v` doubled brace shows {{r}} v shows {r} v\n inside a ````markdown block executes executes\n inside a ````r block executes executes\n\nTwo consequences:\n1. The escape q2 documents for cells -- wrap it in a longer fence -- does not work for inline expressions, so guides/authoring/computations.qmd has to document a different rule for the inline case than for the cell case.\n2. The only working escape is the doubled brace, and q2 displays it verbatim ({{r}}) where Q1 collapses it to {r}. So a q2 author currently has no way to show a reader the text `{r} x`. Note q2's literal rendering is defensible on its own terms -- collapsing doubled braces is what Q-2-50 deliberately dropped so Jinja/Mustache templates render correctly -- which is why the fence-wrapper route, not brace-collapsing, is the right fix.\n\nSHAPE OF THE FIX\nSmall and contained. `mask_code_block_text` (crates/quarto-core/src/engine/nested_cell_mask.rs) is the single rewrite site; add an inline pattern alongside MASK_OPENER_RE that rewrites an inline `{lang}` marker inside an in-scope CodeBlock the same way the opener is rewritten. `unmask` needs no change: UNMASK_OPENER_RE is deliberately not line-anchored (H4, for blockquote prefixes), so it already restores a mid-line occurrence.\n\nMind the plan's invariants: byte-exact restore is load-bearing for reconcile (spec section 'Reconcile -- why byte-exactness is load-bearing'), and the test-seam spec is frozen, so this wants its own tier rather than being bolted onto an existing one.\n\nAlso worth deciding: whether the empty-classes / `markdown`-only display-class predicate is right for inline too, or whether the inline case should be widened (an author writing ````r to show inline syntax gets nothing today -- the E row above).\n\nDocs: guides/authoring/computations.qmd's 'Writing about an inline expression' section documents today's behaviour and should be simplified to match the cell rule once this lands.","status":"open","priority":2,"issue_type":"bug","created_at":"2026-09-04T16:33:12.125548Z","created_by":"Gordon Woodhull","updated_at":"2026-09-04T18:12:17.724568Z","labels":["engine"],"dependencies":{"bd-inline-r-brace-spelling-not-evaluated-lk9s3iwe:discovered-from":{"depends_on_id":"bd-inline-r-brace-spelling-not-evaluated-lk9s3iwe","type":"discovered-from","created_at":"2026-09-04T16:33:12.125548Z","created_by":"Gordon Woodhull"}},"comments":{"c-5lgjuro9":{"id":"c-5lgjuro9","author":"Gordon Woodhull","created_at":"2026-09-04T18:12:17.724568Z","text":"PR #657 — https://github.com/quarto-dev/q2/pull/657 (remote branch bugfix/bd-0gwekaem-nested-cell-mask-inline-expressions, one commit 8e13ecf8b). Pushed after a full cargo xtask verify --skip-rust-tests --skip-hub-tests (WASM/hub-client leg included, all 14 steps green); workspace nextest 13692 passed / 199 skipped and workspace clippy -D warnings were already clean on the same commit."},"c-ndusewmr":{"id":"c-ndusewmr","author":"Gordon Woodhull","created_at":"2026-09-04T16:40:36.539600Z","text":"Dispatched to agent inline-executable-wrapped on workspace-2 (branch braid/bd-0gwekaem-nested-cell-mask-inline-expressions).\n\nConfirmed reproducible on main today with the classic spelling -- an inline `r v` inside a ````markdown display block renders EVALUATED on q2 0.29.0. The brace-spelling half stays latent until bd-inline-r-brace-spelling-not-evaluated-lk9s3iwe merges; the agent was told to cover both spellings and to branch from main rather than from that unmerged branch, which is pending a history squash."},"c-wvs2dbvu":{"id":"c-wvs2dbvu","author":"Gordon Woodhull","created_at":"2026-09-04T17:32:06.840651Z","text":"Fixed on braid/bd-0gwekaem-nested-cell-mask-inline-expressions @ 8e13ecf8b (off main @ 914f20697). Not pushed.\n\nMASKED SPELLING. One rewrite for both inline spellings: the marker is inserted directly after the opening backtick, so ````r v```` becomes ````q2-nested-executable r v```` and ````{r} v```` becomes ````q2-nested-executable {r} v````. unmask deletes the same fixed string. This departs from the strand's suggestion that UNMASK_OPENER_RE could restore the brace form for free: it can, but the classic spelling has no brace to work with and would need its own rule regardless, and two masked spellings buy nothing over one. Insert-only/delete-only also gives the strongest byte-exactness argument available, which the plan says is load-bearing for reconcile.\n\nMASK_INLINE_RE = (^|[^`])`((?:\\{[A-Za-z0-9_]+\\}|r)[ \\t#][^`]+)`\n\n- Brace form: any [A-Za-z0-9_]+, engine-agnostic, matching MASK_OPENER_RE's language class and Q1's per-language executeInlineCodeHandler. Doubled braces excluded by the same first-char rule as openers.\n- Classic form: literal r only. It is knitr's own rmarkdown syntax and is hardcoded to r in all_patterns$md$inline.code; generalizing it to any word would rewrite every ordinary code span in displayed prose.\n- Separator [ \\t#] is the union of knitr's [ #] and q2's [ \\t] -- the mask has to be a superset of every scanner that could claim the expression.\n- The (^|[^`]) guard is the bd-knitr-inline-r-eats-fence-2ofk91x1 shape: without it a nested fence's third backtick anchors a match. It excludes only a backtick, not a backslash -- knitr does not exclude one either, and the YAML-fallback case that motivates INLINE_R_PATTERN's backslash guard is text this mask never sees. Accepted limitation, shared with INLINE_R_PATTERN and Q1: two expressions with no character between them leave the second unmasked.\n\nDISPLAY-CLASS PREDICATE: left narrow (empty or [\"markdown\"]), and filed as bd-tiidc899. Three reasons. It is not inline-specific -- the same predicate gates both rewrites, so widening changes the cell escape too, which 2026-09-02-nested-cell-masking.md scoped out explicitly. Widening for inline only would give the incoherent rule '````r protects your inline expressions but not your nested fences'. And T18 is frozen and revert-bound to the narrow predicate. Narrow is also parity: the strand's own matrix has Q1 executing in a ````r block too.\n\nTESTS: new tier, own hunk ids, T1-T24 untouched. H14 mask rewrites an inline marker, H15 unmask restores it, H16 the guard. T25/T26 render-tier (real knitr); T27 x9 round-trip byte-exactness (classic, brace, tab, hash, two-on-a-line, bare fence, blockquoted, multiline body, opener+inline together); T28 a live cell's backtick-quoted R name untouched; T29 the guard; T30 engine-agnostic brace + doubled brace out of scope; T31 binds to the production scanner -- resolve_inline_r_expressions leaves the masked text alone while the control unmasked text is rewritten, which is the inline counterpart of T14 and starts discriminating the brace half automatically when the {r} alternation lands.\n\nVACUITY FOUND AND FIXED: the first draft of T25 used INLINE-RAN as its runtime-only marker and passed with H14 reverted. q2's classic spelling escapes markdown specials, so the executed value reaches the HTML as INLINE\\-RAN and the contains() check was blind to it. Marker changed to INLINERAN; both render rows confirmed RED under a reverted H14 before the fix was restored. A runtime-only marker used with an inline expression must survive .QuartoInlineRender's escaping unchanged -- recorded in the test file.\n\nVERIFICATION: workspace 13692 passed / 199 skipped, +16 over the 13676 baseline, no removals. cargo clippy --workspace --all-targets -D warnings clean. End-to-end with a freshly built q2 on the strand's repro: the ````markdown row now renders 'Inline: `r v`' where main renders 'Inline: EVALUATED'; the ````r row still renders EVALUATED (bd-tiidc899).\n\nDOCS: no change here. The 'Writing about an inline expression' section the strand names does not exist on main -- it is added by bd-inline-r-brace-spelling-not-evaluated-lk9s3iwe's second commit. Simplifying it belongs with that merge; docs/guides/authoring/computations.qmd on main only documents the cell rule, which stays accurate."}}} {"id":"bd-0h8crv6u","title":"Duplicate-seq self-heal for file documents (backoff + circuit breaker)","description":"Generalize the index-doc duplicate-seq self-heal (bd-6f21d4c6, ts-packages/quarto-sync-client/src/client.ts installDuplicateSeqRecovery) to file documents: a legacy stable-actor client colliding on a FILE doc currently wedges that file's sync permanently for the receiving browser (only remedy: manual IndexedDB wipe). Add per-document recovery with backoff/attempt cap and the state.unavailableFiles retry poll as circuit breaker. Plan: claude-notes/plans/2026-10-22-file-doc-duplicate-seq-recovery.md","status":"closed","priority":1,"issue_type":"feature","created_at":"2026-10-22T10:23:21.422509Z","created_by":"shikokuchuo","updated_at":"2026-10-22T12:40:50.024324Z","closed_at":"2026-10-22T12:40:50.024324Z","close_reason":"Implemented: recoverFileDocument with per-document backoff (3 attempts / 30s cooldown / 300s quiet window) + circuit breaker into the unavailable-files retry poll; routing generalized by documentId in installDuplicateSeqRecovery; change-handler tracking keyed by doc id; recoveryBackoff injectable via ConnectOptions. Tests: new full-stack-file-collision.test.ts (red-then-green gated) + file-collision-recovery.test.ts (backoff, circuit breaker, index + untracked-doc regressions). 191 sync-client + 462 hub-client tests green; build:all green. Plan: claude-notes/plans/2026-10-22-file-doc-duplicate-seq-recovery.md","dependencies":{"bd-6f21d4c6:related":{"depends_on_id":"bd-6f21d4c6","type":"related","created_at":"2026-10-22T10:23:21.422509Z","created_by":"shikokuchuo"}}} -{"id":"bd-0i6fii0k","title":"product-doc-theme (posit-docs) on q2: exploratory assessment + fix plan","description":"Exercise posit-dev/product-doc-theme (the posit-docs custom project type) on a q2 website, compare against Quarto 1 (prima facie correct), and record what works, what breaks, and what fundamentally cannot work as-is. Covers q2 add (not implemented), contributes.project path handling, theme/include rebasing, env shortcodes, and the example site's markdown. Deliverable: claude-notes plan with assessment + fix plan split between q2 and product-doc-theme.","status":"in_progress","priority":1,"issue_type":"task","created_at":"2026-10-21T19:39:12.691821Z","created_by":"Carlos Scheidegger","updated_at":"2026-10-22T15:05:23.080050Z","labels":["extensions"],"dependencies":{"bd-ad7i1pc6:related":{"depends_on_id":"bd-ad7i1pc6","type":"related","created_at":"2026-10-21T19:39:12.691821Z","created_by":"Carlos Scheidegger"}},"comments":{"c-38tbbbtl":{"id":"c-38tbbbtl","author":"Claude Fable 5.1","created_at":"2026-10-22T15:05:23.080050Z","text":"Filed per-finding strands (2026-10-22): F1 bd-74ktvzv7 (logo doubling, P1 bug), F3 bd-t3n1nghb (link-external-*), F4 bd-rfz4e3bw (q2 add), F6 bd-7u2xu4gp ({mermaid} fence warning), F7 bd-hfghrqvh (table caption classes), F8 bd-3z5bubrf (quarto-required), F9 bd-f5l0n6oa (Q-2-9 in config, P4), F10 bd-2pmjmjmj (diagram fig-align). F5 goes to the extension (raw {=html} block). F2 experiment: replacing the footer tags with markdown images (![Posit](/_extensions/posit-docs/assets/images/x.svg){.light-content width=\"65px\\}) works on q2 — all four footer images are copied to _site/_extensions/posit-docs/assets/images/ and load (0 broken footer images in headless Chromium; the only broken image left is the F1 logo), and Q-2-9 drops from 24 to 2 (the in website.title). On Quarto 1 the left item (text+href) renders the markdown and relocates the files to site_libs/quarto-contrib/; but the right item (a markdown link wrapping two images with {#open_preferences_center title=...} attributes and no href) is emitted verbatim, unrendered, so the cookie-preferences link needs a different Q1-compatible spelling or stays raw HTML."},"c-oper168q":{"id":"c-oper168q","author":"Carlos Scheidegger","created_at":"2026-10-21T19:44:22.496560Z","text":"Assessment + plan written: claude-notes/plans/2026-10-21-product-doc-theme-q2.md (commit 9f3c57e44 on braid/bd-0i6fii0k-product-doc-theme-q2). Key findings: custom project types already work (bd-ad7i1pc6) — type: posit-docs resolves, themes/includes/env shortcodes all land; 5/6 example pages render. Gaps: F1 navbar logo src doubled (resolve_metadata_path re-resolves the rebased extension path against _extension.yml's dir; P1 q2 bug), F2 /_extensions/ asset URLs in footer raw not copied/relocated (Q1 relocates to site_libs/quarto-contrib/quarto-project), F3 link-external-icon/newwindow unimplemented, F4 q2 add is a stub, F5 inline