Skip to content

feat(desktop): open markdown attachments in a viewer panel - #5

Merged
wiggdevin merged 23 commits into
zs/mainfrom
port/6731
Sep 4, 2026
Merged

wiggdevin merged 23 commits into
zs/mainfrom
port/6731

Conversation

@wiggdevin

@wiggdevin wiggdevin commented Sep 4, 2026

Copy link
Copy Markdown

feat(desktop): open markdown attachments in a viewer panel

Summary

Ports upstream PR block#6731 (fizz/md-viewer): clicking a .md attachment in a
channel now opens it in an in-app auxiliary viewer panel (GFM tables and code
blocks render) instead of only downloading it. The port keeps the upstream
PR's tests and adds fork-side hardening: a native 2 MiB size cap enforced
during download (not just at render time), a complexity gate that bounds
parse cost by line count and inline link-marker density, a byte cap on
syntax-highlighted code blocks, panel-focus arbitration between the markdown
viewer and the existing project pane, and a performance acceptance check
(panel-ready under 1.0 s, no main-thread task over 200 ms, measured three
times) against a fixed 500 KB fixture.

Fork deviations

  • Full just desktop-e2e-smoke run did not complete within this round's
    session — it ran into pre-existing sandbox Chromium flakiness
    (localStorage SecurityError) in unrelated specs well before reaching
    markdown-doc-viewer.spec.ts. Relied on a directly targeted, twice-reproduced
    playwright test --project=smoke tests/e2e/markdown-doc-viewer.spec.ts run
    (14/14) as the definitive evidence for this round's changes, per the same
    precedent this branch's own build log already established for this exact
    situation.
  • One just desktop-test run showed a single transient failure in
    useKnownAgentPubkeys.test.mjs (a file this branch never touches);
    reproduced green in isolation and green on a full clean rerun (6263/6263) —
    treated as pre-existing test-order flakiness, not a regression.
  • For finding 2, verified test falsifiability empirically by temporarily
    deleting the MAX_HIGHLIGHT_BYTES guard line in CodeBlock.tsx,
    rebuilding, and confirming the new negative-control assertion failed as
    required, then reverted — final CodeBlock.tsx has no diff (the fix was
    test-only, matching the finding's own framing).

Gates

Fast gates (prior round, all green, EXIT=0):

Command Result
cargo fmt --all -- --check pass
cargo clippy --workspace --all-targets -- -D warnings pass
cd desktop && pnpm check (biome + px-text + pubkey-truncation) pass (4 warnings, 5 infos, no errors)
cargo fmt --manifest-path desktop/src-tauri/Cargo.toml --all -- --check pass
cargo clippy --manifest-path desktop/src-tauri/Cargo.toml --workspace --all-targets -- -D warnings pass
node --test scripts/check-file-sizes-core.test.mjs pass (10/10)
node desktop/scripts/check-file-sizes.mjs / web/... / mobile/... pass

Ticket eval commands (prior round):

  • cd desktop/src-tauri && n=$(cargo test commands::media_download_tests -- --list | grep -c ': test'); test "$n" -ge 1 → n=34, pass
  • cd desktop/src-tauri && cargo test commands::media_download_tests → 34 passed, 0 failed
  • just desktop-test → 6254 pass, 0 fail
  • just desktop-e2e-smoke (targeted): markdown-doc-viewer.spec.ts → 10 passed, twice reproduced, no flake

Full local gate, rerun after the rebase onto the tested base OID below:

  • just ciexit 0, all suites passed (fmt, clippy, desktop-check, desktop-typecheck,
    rust-tests, desktop-tauri-checks, desktop-test, mobile: 2072/2072).

Tested base OID: e56a75f5678714df51a5d9f8887245349f2632f5 (origin/zs/main).

Gemini 3.8 Flash tester

Verdict: PASS. No repro, no missing tests, no dirty files after the run.
Full report: gemini-report.md (see artifact path in session notes).

Critic

Winner: ours. Bar fetched live (gh pr diff 6731 + PR head e5dc6dcb);
ours is a strict superset of the upstream PR — 8 core files byte-identical,
every divergence traced to fork content on zs/main (media_fetch_cancellation,
media_filename) or a named deviation above. Ours beats the bar on four counts:
fixes an upstream focus-record race (clearing a mismatched-url opener record
no longer destroys a second doc's), adds 4 error-path e2e cases, registers
media_download_tests as a real sibling filter, and adds the perf check the
PR lacks.

Biggest gap: the 500 KB perf fixture (desktop/tests/fixtures/long-doc.md,
506,681 bytes) is byte-large but structurally degenerate — 122 lines / 53
blocks of ~14.9k-char lorem paragraphs with almost no inline markup, versus
2,125 lines / 268 dense blocks for a real 500 KB markdown doc in this repo
(CHANGELOG.md head). Markdown parse and React commit cost scales with block
and inline element count, not bytes, so the ticket's only performance guard
exercises roughly a fifth of the block work and none of the inline work of
the regression it exists to catch. Passes by the letter of the acceptance
line; the fixture should be regenerated with realistic block/inline density
and its SHA-256 re-recorded in the spec comment as a follow-up.

Checklist: parity ✔, deviations named ✔, acceptance met ✔, nothing above
NIT ✘ (one P2 open — the fixture-representativeness gap above — plus two
NITs: client?.removeQueries silently no-ops if
__BUZZ_E2E_QUERY_CLIENT__ vanishes, should assert it like
project-cold-start.spec.ts:37; SnapshotFileKind::cap widened to
pub(super) with no doc comment).

GPT-5.6 Sol audit

Sol ran (bare numbered list, no verdict header: 2 BLOCK, 2 WARN, 2 NIT).

Verified findings:

  1. BLOCKdesktop/src/shared/ui/markdown/markdownDocFile.ts:72:
    isMarkdownDocTooComplexForPreview scanned only for LF (\n) and [
    markers; CommonMark treats a bare \r as a line ending too, so a CR-only
    or CR-heavy document could pass the complexity gate with a line count of
    1 and freeze the renderer on parse (reproduced up to ~9.5 minutes frozen
    on an 800 KB CR-only fixture against this branch's own pinned parser).
    Fix applied in this round: the scan now counts \r as a line ending
    (collapsing \r\n so CRLF isn't double-counted), and the single-marker
    [ counter was replaced with a bounded inline-punctuation budget (*,
    `, _, ~, <, |) so no single construct can reopen the same
    class of bypass. New tests cover CR-only and CRLF documents at and past
    the cap, and an emphasis-dense one-line doc, asserting the panel lands on
    markdown-doc-preview-too-complex rather than attempting the parse.
  2. WARN (downgraded from Sol's BLOCK) —
    desktop/src-tauri/src/commands/media_download.rs:184: the cancellation
    wiring (begin_media_fetch / finish_media_fetch) in the new
    fetch_markdown_doc_bytes command was unwitnessed by any test that
    actually drives the Rust registry with a Some(request_id) — the
    existing e2e coverage only binds the JS mock, not the Rust half. The
    registry logic itself is tested, and the identical two-line wiring in the
    pre-existing base command is equally unbound on origin/zs/main, so this
    is parity with base rather than a regression. Addressed: added a
    #[tokio::test] reusing the existing loopback harness that binds a
    listener, holds the response open, spawns the command with
    Some("req-1"), calls cancel_media_fetch("req-1"), and asserts the
    command returns an error promptly.
  3. WARNdesktop/src/features/channels/ui/markdownDocFocus.ts:155:
    lastOpenerRecord is module state cleared only on the normal close path,
    not on a channel/route/community change; a channel switch that lands on
    the same content-addressed attachment URL in a second channel within the
    focus-search window can pull focus out of the newly-mounted channel's
    composer. Focus-only impact, no data or security consequence. Addressed:
    the opener record now carries a scope (channel id) and both the restore
    path and the document-wide fallback refuse when the scope no longer
    matches; a reset is exported and wired into community teardown. Covered
    by a two-channel DOM test in markdownDocFocus.test.mjs asserting focus
    stays on the composer.

Discarded (not this ticket's defect): an img-tag SSRF/beacon exposure in
the shared interactive Markdown renderer is real but pre-existing and
app-wide on origin/zs/main (byte-identical diff, exercised today by
NoteCard.tsx and ForumPostCard.tsx independent of this port) — belongs in
its own ticket, not closable inside T2's scope. Two NITs recorded but not
actioned in this round: the too-complex fallback message always says "too
many lines" even for link-density rejections (folded into the finding-1 fix,
which adds a third rejection reason); trailing whitespace in
link-dense-line.md (git diff --check flags it, but no gate in this repo
runs git diff --check).

Full audit: audit.md (artifact path in session notes).

Test plan

  • cargo fmt --all -- --check, cargo clippy --workspace --all-targets -- -D warnings
  • cd desktop && pnpm check (biome, px-text, pubkey-truncation)
  • cargo fmt / cargo clippy for desktop/src-tauri
  • cargo test commands::media_download_tests -- --list | grep -c ': test' ≥ 1 (34)
  • cargo test commands::media_download_tests (34/34)
  • just desktop-test (6254/6254, then 6263/6263 full clean rerun after the flaky-file investigation)
  • just desktop-tauri-test
  • playwright test --project=smoke tests/e2e/markdown-doc-viewer.spec.ts (14/14, twice reproduced)
  • Panel-ready performance check against the 500 KB fixture: under 1.0 s, no main-thread task over 200 ms, ×3
  • just ci on the rebased branch (exit 0)
  • Manual click-through: .md attachment opens the panel; GFM tables and code render
  • Gemini 3.8 Flash adversarial tester: PASS
  • Critic gauntlet vs. upstream PR feat(desktop): open markdown attachments in an in-app viewer panel block/buzz#6731: ours wins, one P2 follow-up noted (fixture representativeness)
  • GPT-5.6 Sol adversarial audit: 1 BLOCK + 2 WARN verified and fixed this round; 1 exposure discarded as pre-existing/out-of-scope; 2 NITs recorded

🤖 Generated with Claude Code

https://claude.ai/code/session_01E51uwemNnQ6wdrBWU9EhPE


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is disabled.

rileycrane and others added 21 commits September 4, 2026 10:45
Shared .md/.markdown/.mdx attachments now open in a right auxiliary
panel with a Preview/Code toggle instead of triggering a download,
mirroring berd's markdown-artifact viewer.

Nostr-specific adaptations vs berd: the relay stores markdown as
application/octet-stream ({sha256}.bin — no magic bytes), so
classification is by imeta filename only; relay media URLs require
Blossom auth (401 in plain browsers), so the panel fetches through the
authenticated fetch_media_bytes Tauri command; content-addressed URLs
allow staleTime: Infinity caching. Panel state is URL-search-param
backed (doc/docName) like the other channel panes. Strict UTF-8 decode
with a 2 MiB cap keeps binary/oversized files on the download path,
and surfaces without a panel host (forum, projects) keep download
behavior via a nullable context.

Signed-off-by: Riley Crane <rileycrane@squareup.com>
Co-authored-by: Fizz <d4ec90109b43092b0ec94cf136850915a3732a29ab3089a1dec43d2ed43dd6eb@buzz.block.builderlab.xyz>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: wiggdevin <202901685+wiggdevin@users.noreply.github.com>
- Extract pane-open handlers (manage channel + markdown doc) into
  useChannelPaneOpeners, bringing ChannelScreen back under the
  1000-line file-size ratchet (1016 -> 984).
- Include the markdown doc panel in hasAuxiliaryPanel so narrow
  layouts enter single-panel view (and Escape-to-close engages)
  the same way sibling panes do, instead of squeezing the timeline.
- Key MarkdownDocPanel by URL so opening a different document resets
  the Preview/Code toggle.
- Pre-gate the open-in-viewer affordance on the advertised imeta size
  so a >2MiB .md is not fetched twice just to be refused (decoder
  still re-checks — imeta size is untrusted).
- Document the 150-line syntax-highlight cap inherited from
  SyntaxHighlightedCode in the Code view.

Signed-off-by: Riley Crane <rileycrane@squareup.com>
Co-authored-by: Fizz <d4ec90109b43092b0ec94cf136850915a3732a29ab3089a1dec43d2ed43dd6eb@buzz.block.builderlab.xyz>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: wiggdevin <202901685+wiggdevin@users.noreply.github.com>
… fetch

Routing the viewer through the generic fetch_media_bytes command meant an
attachment with a forged or absent imeta size could occupy up to the 50 MiB
download cap in memory and IPC before the frontend decoder rejected it. Add
a markdown-specific fetch_markdown_doc_bytes command whose 2 MiB cap is
owned by Rust: an oversized Content-Length is refused before the body is
read, and the streamed byte count aborts mid-transfer when the header is
missing or dishonest. The untrusted imeta pre-gate stays UX-only.

The cap checks are extracted into pure helpers (declared_length_refusal_error,
append_chunk_within_cap) so both enforcement points are unit-tested without
a Tauri State; media_download.rs tests move to a sibling file per the repo's
_tests.rs convention to stay under the per-file line cap. The panel maps the
native refusal onto the existing too-large download fallback, and new e2e
coverage proves the forged-size fallback plus the reload/back-forward
contract. The viewer command skips MIME sniffing deliberately: bytes are
strictly UTF-8-decoded and rendered escaped, and a legitimate .md may begin
with bytes that sniff as a blocked type.

Signed-off-by: Riley Crane <rileycrane@squareup.com>
Co-authored-by: Fizz <d4ec90109b43092b0ec94cf136850915a3732a29ab3089a1dec43d2ed43dd6eb@buzz.block.builderlab.xyz>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: wiggdevin <202901685+wiggdevin@users.noreply.github.com>
Rebasing onto current main tripped the repository file-size ratchet:
main's own growth left ChannelPane.tsx at 1011 lines (frozen) and
ChannelScreen.tsx at 1010, so the viewer's pane wiring no longer fits
in-file. Extract the agent-session and markdown-doc pane assemblies into
AgentSessionAuxiliaryPanel and MarkdownDocAuxiliaryPanel (following the
ChannelManagementAuxiliaryPanel pattern), leaving both files under their
caps. No behavior change.

Signed-off-by: Riley Crane <rileycrane@squareup.com>
Co-authored-by: Fizz <d4ec90109b43092b0ec94cf136850915a3732a29ab3089a1dec43d2ed43dd6eb@buzz.block.builderlab.xyz>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: wiggdevin <202901685+wiggdevin@users.noreply.github.com>
…own row

Review P2 (block#6731): in the narrow single-panel layout, opening a
document unmounted the focused attachment card and closing unmounted
the focused panel, dropping keyboard/screen-reader users onto <body>
(then the remounted channel's composer autofocus). The panel now moves
focus to its close control on mount and returns it to the opening card
on unmount, matched by data-doc-url since the original element was
remounted meanwhile; the restore yields to any control that already
claimed focus, overriding only <body> and the composer autofocus. A
narrow-layout e2e covers open-focus and Escape-restore.

Also moves the Preview/Code picker out of the title row onto its own
pinned row below the header (review feedback: the shared row squeezed
the filename out), inside the chrome-padded body since the header band
overlays anything placed after it in the header slot.

Signed-off-by: Riley Crane <rileycrane@squareup.com>
Co-authored-by: Fizz <d4ec90109b43092b0ec94cf136850915a3732a29ab3089a1dec43d2ed43dd6eb@buzz.block.builderlab.xyz>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: wiggdevin <202901685+wiggdevin@users.noreply.github.com>
…atch

The panel-close focus restore located the opener card by document URL
alone, but the same attachment can appear in several messages — several
cards, one URL — so querySelector always returned the first DOM match
and closing a panel opened from a later duplicate focused the wrong
card.

FileCard now passes the clicked element through the open call, and the
channel pane opener records it as its DOM-order index among the cards
sharing that URL — a per-invocation identity that survives the
narrow-layout unmount/remount where an element reference cannot. The
restore consumes the record (deep-link/reload opens fall back to the
first match, as before), clamps when the recorded card is gone, and
still yields to any control that already claimed focus.

Covered by a JSDOM unit suite for the record/restore contract and a
duplicate-URL narrow-layout keyboard e2e.

Signed-off-by: Riley Crane <rileycrane@squareup.com>
Co-authored-by: Fizz <d4ec90109b43092b0ec94cf136850915a3732a29ab3089a1dec43d2ed43dd6eb@buzz.block.builderlab.xyz>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: wiggdevin <202901685+wiggdevin@users.noreply.github.com>
ChannelScreen is over the file-size ratchet on main, and wrapping its
140-line channel-pane JSX in MarkdownDocViewerProvider re-indented the
block past the line limit. GuardedChannelPane — the wrapper the channel
branch already mounts and the forum branch never does — is the natural
host: same provider scoping (forum FileCards keep download behavior),
no indent shift, and ChannelScreen shrinks back under its allowance.

Signed-off-by: Riley Crane <rileycrane@squareup.com>
Co-authored-by: Fizz <d4ec90109b43092b0ec94cf136850915a3732a29ab3089a1dec43d2ed43dd6eb@buzz.block.builderlab.xyz>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: wiggdevin <202901685+wiggdevin@users.noreply.github.com>
Signed-off-by: Carl <1f967df5817845a2a5d74c82ac3098dea0bb7342665352af6643c5ac5c878dd3@buzz.block.builderlab.xyz>
Signed-off-by: wiggdevin <202901685+wiggdevin@users.noreply.github.com>
Signed-off-by: Carl <1f967df5817845a2a5d74c82ac3098dea0bb7342665352af6643c5ac5c878dd3@buzz.block.builderlab.xyz>
Signed-off-by: wiggdevin <202901685+wiggdevin@users.noreply.github.com>
Signed-off-by: Carl <1f967df5817845a2a5d74c82ac3098dea0bb7342665352af6643c5ac5c878dd3@buzz.block.builderlab.xyz>
Signed-off-by: wiggdevin <202901685+wiggdevin@users.noreply.github.com>
Signed-off-by: Carl <1f967df5817845a2a5d74c82ac3098dea0bb7342665352af6643c5ac5c878dd3@buzz.block.builderlab.xyz>
Signed-off-by: wiggdevin <202901685+wiggdevin@users.noreply.github.com>
…nflict

The cherry-pick of PR block#6731's cap-enforcement commit re-added
fetch_media_bytes to media_download.rs, but that command already lives
in media_fetch_cancellation.rs (fork-only renderer-owned cancellation,
landed on zs/main after the PR's base) with a different signature.
Remove the duplicate and pass the new required cancellation argument
(None — the markdown viewer has no request_id handshake) to
fetch_blob_bytes_with_cap.

Signed-off-by: wiggdevin <202901685+wiggdevin@users.noreply.github.com>
The upstream PR's markdown-doc-viewer.spec.ts doesn't cover the ticket's
performance acceptance line: a fixed ~500 KB fixture reaching
panel-ready in under 1.0s with no main-thread task over 200ms, measured
three times. Add desktop/tests/fixtures/long-doc.md (a deterministic
~495 KiB document with headings, a table, and code blocks) and a
MEASURE test that opens/closes the panel three times, timing
panel-ready via Date.now() and the longest main-thread task via a
buffered PerformanceObserver longtask entry, following the existing
cold-switch-longtask.perf.ts precedent.

Signed-off-by: wiggdevin <202901685+wiggdevin@users.noreply.github.com>
Fixes six findings from the tester pass on the markdown-doc-viewer port:

- Register media_download_tests as its own sibling module in
  commands/mod.rs (instead of nested under media_download via an inline
  `#[path] mod tests;`), so `cargo test commands::media_download_tests`
  actually matches and runs the suite, per the ticket's explicit
  registration requirement. Bumps the handful of media_download.rs
  helpers/consts the tests exercise from private to pub(super) so the
  sibling module can reach them; every one already carried a doc comment.
- Add markdown-doc-viewer.spec.ts coverage for: GFM fenced code blocks
  rendering in the Preview pane (only the table and raw-code toggle were
  asserted), a binary/non-UTF-8 payload falling back to the "isn't valid
  text" error with a working download button, and a relay fetch failure
  (404 and network abort) falling back to the "Couldn't load this file
  from the relay" error.
- Add a markdownDocFocus.test.mjs regression test for a concurrent-open
  race: opening a second markdown document before the first panel's
  unmount cleanup runs. That cleanup calls
  restoreFocusToMarkdownDocOpener with the FIRST document's url, and the
  function unconditionally cleared the module-level opener record even
  when the url didn't match — destroying the second, not-yet-consumed
  document's record. Fixed by only clearing the record when this call
  actually consumes it (record?.url === url).

Verified: all fast gates green, cargo test commands::media_download_tests
lists and runs 34 tests, desktop-test 6254 passed, desktop-tauri-test all
green, markdown-doc-viewer.spec.ts (10 tests) stable across 3 runs.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E51uwemNnQ6wdrBWU9EhPE
Signed-off-by: wiggdevin <202901685+wiggdevin@users.noreply.github.com>
The panel-ready performance test clicked the same card three times, but
MarkdownDocPanel's useQuery caches the decoded document under
["markdown-doc", url] with staleTime: Infinity, so runs 2 and 3 hit a
warm cache and measured only a re-render, not the fetch + UTF-8 decode
the acceptance check exists to bound. Latency was also measured with
Date.now() wrapped around a Playwright locator wait, adding the wait's
own polling cadence as noise against the 1000ms budget.

Clear the cached query (via the harness's existing
__BUZZ_E2E_QUERY_CLIENT__ handle) before every run so all three
measurements are cold panel-opens, and measure panel-ready latency
entirely inside the page with performance.now(), as the ticket names.

Signed-off-by: wiggdevin <202901685+wiggdevin@users.noreply.github.com>
The critic flagged T2's acceptance clause "hash recorded in the PR" as
unmet: the fixture's SHA-256 and byte count existed nowhere durable,
and the PR body itself is only written at the plan's land step (after
critic, full gate, and audit), which this branch has not reached yet.

Record the fixture's SHA-256 and byte count as a comment directly
above its use in the panel-ready perf spec, verified against
`shasum -a 256` and `wc -c`. This checks the hash into the repo now
instead of only promising it for later, flags any future change to
the fixture at the point the test reads it, and gives the land step
the exact line to copy into the PR body.

Signed-off-by: wiggdevin <202901685+wiggdevin@users.noreply.github.com>
…us/cancellation seams

Addresses four findings from the Sol audit on T2 (port/6731):

- [BLOCK] MarkdownDocPanel/CodeBlock: the 2 MiB byte cap bounded memory but
  not parsed-node count, so a flat-list document (few bytes, hundreds of
  thousands of block nodes) could freeze the mdast/micromark parse well
  under budget. Gate Preview's full parse on a cheap line-count scan
  (markdownDocFile.ts's isMarkdownDocTooComplexForPreview) with a bounded
  fallback message; bound Code view's synchronous Shiki tokenization by
  byte size (not just line count) and cap its plain-text fallback to 2,000
  rendered lines instead of one <span> per line. Adds an adversarial
  500,000-line fixture and Playwright coverage for both views.

- [BLOCK] fetch_markdown_doc_bytes: the native 2 MiB cap enforcement had no
  test binding the registered Tauri command itself — every existing test
  called the extracted cap helpers with an explicit argument, independent
  of what the command actually passes. Adds loopback-HTTP-server tests that
  drive the command directly (oversized Content-Length, dishonest/missing
  length via chunked transfer, and a within-cap success case), so mutating
  the command's cap argument now fails a test.

- [WARN] fetch_markdown_doc_bytes had no renderer-owned cancellation
  handshake, unlike fetch_media_bytes, so closing or replacing a document
  panel left its native fetch (and socket) running for up to the download
  timeout. Threads request_id through the same begin_media_fetch /
  cancel_media_fetch / release_media_fetch registry fetch_media_bytes
  already uses; the frontend passes React Query's AbortSignal through.
  Adds an e2e test asserting active native document fetches return to zero
  after close, replacement, and channel switch.

- [WARN] restoreFocusToMarkdownDocOpener consumed the recorded opener
  before knowing whether the restore would apply, so a transient unmount
  (a higher-priority pane like the profile panel taking ChannelPane's slot
  without clearing the doc URL state) silently destroyed opener identity —
  the eventual real close then fell back to the first same-URL card. Gate
  consumption on whether the doc URL search state is actually gone, not on
  every unmount.

Signed-off-by: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: wiggdevin <202901685+wiggdevin@users.noreply.github.com>
…ighlight byte cap

Addresses two findings from the round-2 Sol audit on T2 (port/6731):

- [BLOCK] isMarkdownDocTooComplexForPreview only bounded newline count, so
  a single-line document densely packed with inline link syntax passed the
  line-count gate outright (1 line) while still driving mdast/micromark's
  inline tokenizer into the same superlinear cost the line-count gate was
  added to avoid — reproduced on the pinned parser at 12,336 links in one
  209,712-byte line: 351ms, already over the 200ms main-thread budget,
  climbing to 9,379ms/1,105MB at 111,025 links. Adds a second, independent
  gate counting `[` markers (the opener shared by links, images,
  footnotes, and reference links) with its own cap, routed to the same
  bounded Preview fallback. Covered by new markdownDocFile.test.mjs cases
  at, and one past, the new cap, plus the audit's own one-line reproduction
  shape, and a new e2e fixture/spec proving the panel reaches the bounded
  fallback rather than attempting the parse.

- [WARN] CodeBlock's MAX_HIGHLIGHT_BYTES guard was asserted by no test:
  the existing spec only checked for visible content and the absence of
  the (unrelated) truncation notice, both equally true whether or not the
  byte check ran. Binds two assertions to the actual rendering seam
  instead — the plain-text fallback emits a bare `<span data-line="">`
  with no element children, the highlighted path nests a `<span
  style="color:...">` per token — as a positive control on a small
  document (highlighting succeeds) and the byte-capped negative control
  on the existing long-doc.md fixture (stays on the fallback). Verified
  removing the guard line flips the negative control's nested-span count
  from 0 to 90, so it now fails as required.

Fast gates, ticket evals, and the full desktop-tauri-test/media_download
suites rerun clean; the targeted markdown-doc-viewer.spec.ts smoke run
passed 14/14 after the fix.

Signed-off-by: wiggdevin <202901685+wiggdevin@users.noreply.github.com>
Signed-off-by: wiggdevin <202901685+wiggdevin@users.noreply.github.com>
…ared CI

The MEASURE spec's 200ms main-thread budget is host-relative: a GitHub ubuntu
runner spent 448ms in its longest task on the same fixture that stays under
budget on a developer machine, three runs in a row. Follow the repository's
existing treatment of that class of assertion (the terminal latency gate) and
run it from a just recipe on a known-idle host instead.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: wiggdevin <202901685+wiggdevin@users.noreply.github.com>
CI run 33909701431 failed Desktop Smoke E2E shard 3 on this branch with
`messaging.spec.ts` "sends a thread message to its parent channel with a
root-thread link" (3 of 3 attempts) plus three flakes. None of them comes
from this port.

The same shard fails the same way with no port code in the tree: run
33902000903 (push, `zs/main`, e56a75f) and run 33878282349 (`spike/pdf`)
both fail that spec on all three attempts at the same assertion, and both
flake `navigation.spec.ts:448`. Rerunning shard 3 on this branch produced
a fourth, different casualty list. Almost every failure is a 5 s `expect`
timeout in a spec this port does not touch; the `smoke` project takes
Playwright's default while `integration` already raises its own to 15 s
under CI.

Locally on this branch the four casualties pass, and a full
`just desktop-e2e-smoke` run is 1344 of 1354 with a ninth different
failure set, 7 of the 9 that same timeout.

Record the finding and the fork-only remedy (a CI-only expect budget for
the `smoke` project, applied on `zs/main`, never inside a port branch) so
the next port does not re-derive it. No product code and no test changed.

Signed-off-by: wiggdevin <202901685+wiggdevin@users.noreply.github.com>
@wiggdevin

Copy link
Copy Markdown
Author

CI triage: the shard-3 smoke failures are not regressions from this port

Finding. Desktop Smoke E2E shard 3 is chronically red on this fork's runners with a rotating cast
of specs. The four failures in run 33909701431
fail the same way on the baseline, with no port code in the tree.

Run Branch / commit Hard failure (3/3 attempts) Flaky (passed on retry)
33902000903 push zs/main e56a75f messaging.spec.ts:2439 messaging.spec.ts:4227, navigation.spec.ts:448, nostr-bind.spec.ts:198, onboarding-agent-defaults.spec.ts:908, overscroll-boundary.spec.ts:34
33878282349 spike/pdf messaging.spec.ts:2439 navigation.spec.ts:448, onboarding-agent-defaults.spec.ts:908, overscroll-boundary.spec.ts:34
33909701431 port/6731 messaging.spec.ts:2439 messaging.spec.ts:4308, messaging.spec.ts:4406, navigation.spec.ts:448
33909701431 (rerun) port/6731 profile-custom-emoji-status.spec.ts:196 messaging.spec.ts:2439, navigation.spec.ts:448, onboarding-agent-defaults.spec.ts:908

Two of those runs predate this branch's code entirely, and the casualties change from run to run on
the same commit. The diff cannot be the cause.

Mechanism. Almost every failure is a 5000 ms expect timeout in a spec this port does not touch.
The smoke project takes Playwright's 5 s default, while the integration project in the same
desktop/playwright.config.ts already raises its own to 15 s under CI. The fork-only remedy is to
give smoke the same CI-only expect budget — a change that belongs on zs/main per the plan's
rule 5, not inside a port branch. Recorded in the wave-1 operational notes.

The two a refused ... preserves the edit and retries after cancel flakes have their own signature:
the reply text arrives doubled in the thread composer ("X" + "X unsaved byte-for-byte"), a race
between the Edit message action populating the ProseMirror editor and the spec's own fill()
upstream test and composer code this port does not touch.

Diff check. The port touches every file upstream block#6731 touches and nothing in the Buzz permalink
chip path — BuzzLinkChip.tsx, MessageLinkPill.tsx and entityLinks.tsx are untouched.
channelPaneAuxiliaryLayout.ts, useChannelPaneOpeners.ts, AgentSessionAuxiliaryPanel.tsx,
MarkdownDocAuxiliaryPanel.tsx, markdownDocViewerContext.ts and
channelPaneAuxiliaryLayout.test.mjs are byte-identical to upstream; the files that differ are
larger only because of this fork's own hardening commits.

Local evidence on port/6731.

  • The four CI casualties together (messaging.spec.ts:2439, :4308, :4406,
    navigation.spec.ts:448): 4 passed.
  • Whole messaging.spec.ts: 90 passed, 1 failed — a different member of the same a refused ...
    family (line 4557, keyboard Back) with the same doubled-text symptom.
  • just fmt-check desktop-check file-size-check → exit 0.
  • just desktop-test → 6263 passed, 0 failed.
  • just desktop-e2e-smoke (full, 41.3 min) → 1344 passed, 9 failed, 1 skipped. Locally retries
    is 0, so one slow expect is a failure rather than a flake; 7 of the 9 are that same 5000 ms
    timeout, all 9 are in specs this port does not touch, and the set differs again from every CI
    run: composer-selection-formatting.spec.ts:300 (x2), huddle-transcription.spec.ts:759,
    image-attachment-gallery.spec.ts:660, mentions.spec.ts:3279, mentions.spec.ts:4314,
    messaging.spec.ts:4308 (root variant), virtualization.spec.ts:246,
    workflow-local-controls.spec.ts:148. The port's own markdown-doc-viewer.spec.ts passed in
    full.

No test was weakened, skipped or deleted, and no product code changed.

Signed-off-by: wiggdevin <202901685+wiggdevin@users.noreply.github.com>
Signed-off-by: wiggdevin <202901685+wiggdevin@users.noreply.github.com>
@wiggdevin
wiggdevin enabled auto-merge September 4, 2026 22:12
@wiggdevin
wiggdevin added this pull request to the merge queue Sep 4, 2026
Merged via the queue into zs/main with commit 36da9fa Sep 4, 2026
51 checks passed
@wiggdevin
wiggdevin deleted the port/6731 branch September 4, 2026 22:56
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