Conversation
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>
CI triage: the shard-3 smoke failures are not regressions from this portFinding. Desktop Smoke E2E shard 3 is chronically red on this fork's runners with a rotating cast
Two of those runs predate this branch's code entirely, and the casualties change from run to run on Mechanism. Almost every failure is a 5000 ms The two Diff check. The port touches every file upstream block#6731 touches and nothing in the Buzz permalink Local evidence on
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>
feat(desktop): open markdown attachments in a viewer panel
Summary
Ports upstream PR block#6731 (
fizz/md-viewer): clicking a.mdattachment in achannel 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
just desktop-e2e-smokerun did not complete within this round'ssession — 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.tsrun(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.
just desktop-testrun showed a single transient failure inuseKnownAgentPubkeys.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.
deleting the
MAX_HIGHLIGHT_BYTESguard line inCodeBlock.tsx,rebuilding, and confirming the new negative-control assertion failed as
required, then reverted — final
CodeBlock.tsxhas no diff (the fix wastest-only, matching the finding's own framing).
Gates
Fast gates (prior round, all green,
EXIT=0):cargo fmt --all -- --checkcargo clippy --workspace --all-targets -- -D warningscd desktop && pnpm check(biome + px-text + pubkey-truncation)cargo fmt --manifest-path desktop/src-tauri/Cargo.toml --all -- --checkcargo clippy --manifest-path desktop/src-tauri/Cargo.toml --workspace --all-targets -- -D warningsnode --test scripts/check-file-sizes-core.test.mjsnode desktop/scripts/check-file-sizes.mjs/web/.../mobile/...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, passcd desktop/src-tauri && cargo test commands::media_download_tests→ 34 passed, 0 failedjust desktop-test→ 6254 pass, 0 failjust desktop-e2e-smoke(targeted):markdown-doc-viewer.spec.ts→ 10 passed, twice reproduced, no flakeFull local gate, rerun after the rebase onto the tested base OID below:
just ci→ exit 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 heade5dc6dcb);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_testsas a real sibling filter, and adds the perf check thePR 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.mdhead). Markdown parse and React commit cost scales with blockand 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?.removeQueriessilently no-ops if__BUZZ_E2E_QUERY_CLIENT__vanishes, should assert it likeproject-cold-start.spec.ts:37;SnapshotFileKind::capwidened topub(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:
desktop/src/shared/ui/markdown/markdownDocFile.ts:72:isMarkdownDocTooComplexForPreviewscanned only for LF (\n) and[markers; CommonMark treats a bare
\ras a line ending too, so a CR-onlyor 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
\ras a line ending(collapsing
\r\nso 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 sameclass 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-complexrather than attempting the parse.desktop/src-tauri/src/commands/media_download.rs:184: the cancellationwiring (
begin_media_fetch/finish_media_fetch) in the newfetch_markdown_doc_bytescommand was unwitnessed by any test thatactually drives the Rust registry with a
Some(request_id)— theexisting 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 thisis parity with base rather than a regression. Addressed: added a
#[tokio::test]reusing the existing loopback harness that binds alistener, holds the response open, spawns the command with
Some("req-1"), callscancel_media_fetch("req-1"), and asserts thecommand returns an error promptly.
desktop/src/features/channels/ui/markdownDocFocus.ts:155:lastOpenerRecordis 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.mjsasserting focusstays 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 byNoteCard.tsxandForumPostCard.tsxindependent of this port) — belongs inits 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 --checkflags it, but no gate in this reporuns
git diff --check).Full audit:
audit.md(artifact path in session notes).Test plan
cargo fmt --all -- --check,cargo clippy --workspace --all-targets -- -D warningscd desktop && pnpm check(biome, px-text, pubkey-truncation)cargo fmt/cargo clippyfordesktop/src-tauricargo 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-testplaywright test --project=smoke tests/e2e/markdown-doc-viewer.spec.ts(14/14, twice reproduced)just cion the rebased branch (exit 0).mdattachment opens the panel; GFM tables and code render🤖 Generated with Claude Code
https://claude.ai/code/session_01E51uwemNnQ6wdrBWU9EhPE
Need help on this PR? Tag
@codesmith-botwith what you need. Autofix is disabled.