Skip to content

release: 0.5.29 (combines #30, #31, #32, #34, #35, #37, #39, #40) - #41

Merged
QuicksilverSlick merged 44 commits into
mainfrom
release/0.5.29
Oct 13, 2026
Merged

QuicksilverSlick merged 44 commits into
mainfrom
release/0.5.29

Conversation

@QuicksilverSlick

Copy link
Copy Markdown
Owner

One PR that combines every open PR, so CI runs once instead of eight times. Merge it with a merge commit (not squash). Each PR below is then marked merged automatically, because its head commit lands in main.

PR What
#30 Probe the Codex engine codex-acp actually runs
#31 No global codex CLI needed for Codex agents
#32 Release page says which file to download; one version per page
#34 Bench card text readable on the phone (CARD_FORMAT 4: plain text, no grey box)
#35 Clickable Bench cards and the Bench screen on desktop; duplicate keycap pills hidden
#37 Guest guide
#39 Guest mode, About this channel, clearer agent hints
#40 Escape guest text in buzz-acp conversation context

Already tested locally on Windows (this exact head, 7aa8c30):

  • Desktop: 6954/6954 node tests and 160/160 jsdom tests pass.
  • Desktop crate: the Rust lib suite passes, 3356 tests at the previous head plus the bench tests at this head.
  • buzz-acp: failures match main's known Windows baseline exactly.
  • A local 0.5.28 installer built from this head is installed and in use.

This CI run is the first on macOS and Linux since the upstream sync.

After merging, publish with version 0.5.29. The owner's PC already runs a local 0.5.28.

🤖 Generated with Claude Code

QuicksilverSlick and others added 30 commits September 27, 2026 10:30
Codex readiness and the cached auth badge ran `codex login status` with
whatever `codex` was first on PATH. codex-acp never uses that binary: it
spawns `CODEX_PATH` when set, else the @openai/codex it bundles. When the
global CLI lags the Codex desktop app's config (e.g. an old CLI rejecting
`model_reasoning_effort = "ultra"`), a working agent parked in setup mode
with a false "config.toml is invalid" card.

Both probes now go through `cli_probe::probe_command`: CODEX_PATH (agent
env over the inherited app env; empty = unset, as in codex-acp), else
`node <codex-acp>/node_modules/@openai/codex/bin/codex.js` resolved the
way Node resolves it from the adapter package (Windows .cmd/sh shim
layout and unix bin symlink), else the PATH `codex`. ConfigInvalid
classification is unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: QuicksilverSlick <russelledeming@gmail.com>
codex-acp spawns CODEX_PATH whenever it is set, so a CODEX_PATH that does
not resolve must not fall back to the bundled or PATH engine (which would
report Ready for an agent that then fails to spawn). Readiness now surfaces
it as a missing binary naming `CODEX_PATH=<value>` instead of asking for a
`codex login`; the badge probe skips it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: QuicksilverSlick <russelledeming@gmail.com>
Port of the buzz-acp half of upstream block#7594 (Salman Mohammed).
The desktop readiness engine can emit `Requirement::MissingBinary`, and
this change makes it do so for a CODEX_PATH that does not resolve. The
fork's setup-mode parser did not know the variant, so the setup listener
exited on a malformed BUZZ_ACP_SETUP_PAYLOAD and the agent crash-looped
instead of posting a nudge (upstream issues block#4628, block#7613).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: QuicksilverSlick <russelledeming@gmail.com>
The codex runtime declared `underlying_cli: Some("codex")`, so an
installed codex-acp with no `codex` on PATH classified as CliMissing:
readiness parked the agent ("codex CLI is missing"), Agent runtimes hid
the Connect button (shown only when Available) and offered to install a
CLI the agent never runs. Every supported codex-acp (>= the 1.1.7 floor)
depends on @openai/codex and runs that bundled engine unless CODEX_PATH
is set; the readiness and badge probes already use it.

Drop the CLI requirement so an installed adapter is Available, and lead
the logged-out copy with the in-app Connect flow, since `codex login`
needs the optional CLI.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: QuicksilverSlick <russelledeming@gmail.com>
The release page now opens with a table: Windows PC, Mac with an Apple chip, Mac with an Intel chip, each linking its exact file, plus how to tell which Mac you have and step-by-step install for both. The text lives in scripts/dreamforge-release-notes.md; the publish job fills in the version and links, sets the title to "Dreamforge <version>", rewrites the notes on every publish, and after latest.json is uploaded removes the previous version's files so the page only ever shows one set.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Publish: the notes edit now runs before latest.json goes live, and the old-file cleanup after it only warns (a failure there used to turn a shipped release red, and a rerun is refused as not newer). The asset list is captured without a pipe, since the publish job's bash has no pipefail. The pre-publish check now also requires both DMGs the page links.

Page: Mac steps say to click Done, not Move to Trash, that Open Anyway lasts about an hour, and that it asks for the login password; chip guidance covers A-series Macs and matches About This Mac's Chip and Processor lines; Windows steps cover Edge's isn't-commonly-downloaded prompt and Smart App Control; the certificate wording is corrected; and updates are described as the Ready to update! card they are.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
bench_cards returns a read-only camelCase snapshot of state.json through the service's own loader: enabled (false, and nothing else, without the opt-in folder), channel, writer and owner pubkeys, lastError, lastTick and per item the drop fields, the live card id, the answer and what the card records. No key material.

bench_answer(itemId, option) answers a pending item on its live card as the owner: it reads the owner's kind 7s on the card through the poll's own /query (Bench relay, writer NIP-98, lazy owner tag), plans with the pure plan_answer, then posts the keycap first and the kind 5 removals after through post_as_owner, so a failed delete never loses the new answer. The running tick records it as it would a phone tap; the command writes no state.

item::reaction_target is factored out of pick_answer so both read a reaction's card the same way. Both commands are registered in lib.rs; this app has no AppManifest command list, so no capability entry is needed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: QuicksilverSlick <russelledeming@gmail.com>
The phone draws a fence as 13px monospace that never wraps, so a card's title and options were a sideways scroll. The title is now one bold line and the options a numbered list, both outside the fence; only the agent-written summary stays fenced. CARD_FORMAT 3 reposts each live unanswered card once, quietly; answered cards keep their answer.

plain_title becomes plain_text(t, max), shared by board lines and card title, options and link labels. Holes closed on the way: '_' formed emphasis on the desktop; ';' let an entity such as &num; decode into a live #channel or buzz:// link there; a dropped character could join w[w]w. into an autolink, so rejected characters now become spaces. Text after a card's '1. ' never opens a nested block on either app: it starts at a letter, digit or quote, a leading 1. or 1) loses its mark, and trailing dashes go (gpt_markdown draws any line ending in --- as a rule).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A message in #bench renders as a Bench card instead of its markdown body only when the snapshot's channel matches, its id is a live card's cardEventId and the Bench writer key signed it (benchRowState). Every other row renders as before.

The card is built from Card, Badge and Button. It has a severity chip, the title and body as plain React text (agent text stays inert), the due date, https and buzz:// links, and one button per option. A click calls bench_answer with the 0-based option, or null on the recorded option to undo. The card shows Saving... until the snapshot's recorded value moves, and shows an inline error on a command error or after 15 s. Once answered it shows Recorded: n. option with the time. Decision and status items, and stale, superseded or non-owner cards, are read-only.

useBenchCards polls bench_cards every 3 s while visible, using one shared timer for all rows instead of one react-query interval per observer. Rows outside #bench read the cached snapshot. Keycap reaction pills stay under the card because they are the record shared with the phone. The E2E bridge mocks bench_cards (configurable) and bench_answer.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: QuicksilverSlick <russelledeming@gmail.com>
gpt_markdown ends a code block only at a line of exactly three backticks, so the longer fence fence() picks never shielded a body with an inner ``` line: the phone closed the block there, drew any [label](url) after it as a live link, and the Bench's own closing fence then swallowed the options and the answer hint into unwrapped monospace. Backticks in the summary now become U+02CB, so the fence is always three backticks and nothing inside can close it on either app.

The phone never wraps a fence and shows about 26 columns of it on a 360 dp screen, so the summary, which holds the question, is hard-wrapped at FENCE_WIDTH (26) on spaces, longer words cut, newlines kept.

plain_text puts a space after every ':', so no :shortcode: survives in a title, option, link label or board line for the phone to resolve against any community member's emoji set. It folds curly quotes, en and em dashes and the ellipsis to ASCII first, so "Don't" no longer reads "Don t". validate() rejects an option with no plain letter or digit, so the owner never gets a bare "1." beside a keycap; the 38 exported claude.ai drops still validate.

CARD_FORMAT stays 3: format 3 has not shipped.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A Bench entry in the sidebar's primary menu, under Inbox, opens a new /bench route. The entry shows only while bench_cards says the Bench is enabled. Its badge (the Inbox count pill, now shared as SIDEBAR_COUNT_BADGE_CLASS) counts the items that need the owner, refreshed every 15 s by its own query and faster while the screen or #bench polls.

The screen follows the Agents/Workflows layout (PageHeader, SectionHeader) and reuses BenchCard. Sections follow the pinned board's order and sorting: Needs you (pending, current, unanswered, by severity, order, id), Answered, waiting for action (oldest answer first), Decided and Where things stand (by area, order, id). Each section has empty-state copy. The page shows the Bench's last update time, a visible error line when lastError is set, and a note counting stale or superseded items, which the board also folds. Cards answer through bench_answer with the Bench's own relay and keys, so the screen works in any community; only the owner's identity gets live buttons.

benchCardMode now says "Waiting for the Bench to post this card" for an unanswered item with no live card, which only the screen can show. Its answered counterpart keeps "no longer live".

Tests: pure section/badge tests, render tests of the real screen and sidebar entry against mocked IPC, a deriveShellRoute case, and a smoke spec (bench-screen.spec.ts) that opens the screen from the sidebar, answers a card, checks the bench_answer payload, then swaps the mock snapshot and sees the card recorded under Answered.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: QuicksilverSlick <russelledeming@gmail.com>
Nothing in Buzz or Dreamforge explains the app to an invited guest. Add docs/guest-guide.md: Mac and iPhone first, then Windows and Android. It covers the basic words, public and private channels, getting each app, joining with an invite link, using a computer and a phone as the same member, seeing what a channel is for, asking an agent and what silence means, notifications, reporting a message and who to ask for help. Every UI step was checked against desktop/src at origin/main and mobile/lib at mobile-v0.20.0-rc.1; the header line and sidebar hover are marked as Dreamforge 0.5.22 and later.

Link it from the top of the README, and point the Welcome canvas link (labelled Dreamforge user guide, opening Block's developer README) at the guide as Dreamforge guest guide. Existing Welcome canvases keep their saved text.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: QuicksilverSlick <russelledeming@gmail.com>
…; retired cards keep their answer

bench_answer planned against the owner's reactions only, while the poll picks the newest keycap among the owner and every members.txt key. A phone-key tap that outranked the owner's keycap, or that a clear could not remove, made bench_answer publish nothing and return Ok, so the card sat on Saving... and then timed out on every retry.

answer_card now queries the poll's approvers and runs pick_answer on what the plan leaves standing. An owner keycap outranked by a newer tap is deleted and then posted again: the relay refuses an active identical reaction as a duplicate, so the delete has to come first. A clear that another key's tap would survive is refused with "answered from another device; change or undo it there", before anything is published. The new keycap is stamped one second past the newest tap left standing, so a phone clock running ahead cannot keep outranking it. A tap more than MAX_CLOCK_LEAD_SECS ahead is refused rather than chased.

On the Bench screen, an answered card that was retired after 24 h, or deleted by hand, lost its recorded option, because forget_card clears it and keeps the answer. Such a card showed no selection and no Recorded line. It now shows the Bench's answer, read-only. The reducer still settles on recorded alone. A selected but disabled option button no longer says "click to undo".

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Drop the guessed 0.5.22 version from the channel-purpose bullets and describe what a guest sees instead. Put the desktop join steps in the order the app shows them: one Skip for now ends machine setup, and the age and terms boxes appear before Next works. Say Join appears when you point at a channel in Browse channels. Explain the post-join warning and Back it up now button in step 5. Add the Safari Open prompt, the camera prompt, and the hour-long Open Anyway window on a Mac. Say that refusing the Mac notification prompt switches Desktop alerts off, so it has to be turned back on. Remove the agent notice that no app shows, and mention agents that are switched off.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Covers the verifier's surviving mutation: render_card's empty-title guard. Fails with the guard removed, passes with it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…annel

Everyone who finished community onboarding got a private Welcome channel, the Fizz, Honey and Pollen Welcome Team, and a kickoff telling them to connect an AI provider. For someone invited into a community whose owner already set up its channels and agents, that is a dead end: the team runs on the guest's own AI account, and the guest never sees the channel made for them.

An onboarding is an invite join when its transaction carries an invite code. Every invite entry point sets one (an opened invite link, an invite pasted into Join a community, Add a community or the membership-denied screen), and the flow only moves past claiming once the relay accepts it. When such a person finishes the profile step, or skips it because their identity already has a profile, the app asks the relay for their role and their channels. Unless the relay lists them as the owner or an admin, they enter as a guest: no starter team screen, no Welcome channel, no Welcome Team, no kickoff and no starter channels created in their name. They land in the most recently active channel they belong to other than #general and #welcome-everyone, else #welcome-everyone, else Inbox. Guest mode is remembered per identity and community, so later launches never create the Welcome channel either, and a private Welcome channel a guest happens to belong to never provisions the team. Both relay questions are bounded; if the relay does not answer, the invite decides and the guest lands in Inbox.

Owners, people who create a community and anyone connecting without an invite keep today's Welcome experience. Unit tests cover the invite and role rules, the stored guest flag, the landing choice and the bounded lookups; the invite E2E spec covers a guest landing from the profile step, a guest whose profile already exists, and an admin who uses an invite keeping the starter team.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…invite

First-run setup asks every new person to connect an AI provider before they reach a community. Someone opening an invite link is joining a community whose owner already set up its agents, and Settings > Agents still offers the same setup whenever they want their own.

When the pending community link carries an invite code, onboarding now goes straight from the identity key to the community, exactly as Set up later would: fresh keys continue to the community profile, and imported or phone-recovered keys skip it as before. Links without an invite, and everyone who starts setup without a link, still see the step. The invite E2E spec follows a new person from an invite link through key creation, past the provider step and profile, into their channel.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… of @fizz

The hint above the composer in #welcome-everyone reads "Mention @fizz or another teammate whenever you want their help." Guests who joined by invite have no Fizz: they never get the Welcome Team. For them the hint now reads "Type @ and an agent's name to ask it for something." when they can see any of the community's agents, and there is no hint at all when they can see none. Everyone else keeps the @fizz hint, its animation and its Nice work. confirmation.

A pure helper picks the hint from guest mode and whether any agents are visible, with a table test over every combination and a test that pins the guest copy; the invite E2E spec checks both guest cases in #welcome-everyone.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e messages

A channel's canvas is the owner's guide to the channel, but it was reachable only from Channel settings, behind a Canvas row most guests never find. Channels whose canvas has content now get an About this channel button in the header. It opens the canvas read-only in the side panel, with the same renderer Channel settings uses; editing stays in Channel settings.

About opens by itself the first time this device shows a channel whose canvas has content, and is remembered per channel in local storage (the most recent 500 channels). It does not open by itself over a thread, profile, agent activity or settings panel that is already open, such as from a link to a thread, nor in a window too narrow to show it beside the messages, nor in the private Welcome channel, which has its own guided start; those visits leave it unseen, so the next plain visit opens it. About shares the side panel with Channel settings through the existing channelManagement URL value ("about" next to "1"), so everything that closes settings, such as opening a thread or a profile, closes About too, and back and forward restore it.

Unit tests cover the content check, the auto-open rule and the bounded seen list, including refused storage. A new E2E spec covers the first-visit open, staying closed on return and after a restart, read-only content, taking turns with Channel settings, a thread link, a narrow window and channels without a canvas; removing the auto-open guards fails two of its tests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The @-mention list showed an agent's name, "agent" and who manages it, so a guest could not tell what an agent is for. Each agent row now has a second line with the agent's public description (its profile About text, or the persona description for an agent on this desktop that has no About yet), cut to one line, and the mention button is described by it for screen readers.

When an agent's known reply setting leaves out the person typing (Only me, or Selected people without them), the row says "Answers only <owner>" in place of "managed by <owner>". The setting comes only from data the app already has: this desktop's agents, and the owner's published setting in the community's agent directory for agents run elsewhere. An agent whose setting is unknown gets no note. About text comes from each agent's public profile, looked up for at most the first 20 agents in the list, in its own cache entry under the user-profile key so profile refreshes cover it.

Messages from agents that answer anyone carried an amber warning triangle on the avatar; that is now a neutral globe with the same label.

Unit tests cover the one-line description, where the reply setting comes from, every combination that leaves the viewer out, the note text, the lookup cap, and the list rows (description, note, screen-reader description, people rows untouched). A new E2E spec covers a description from an agent run elsewhere and the neutral icon.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…d Ctrl+J

A short plain-language pass on strings a guest meets:

- "relay" becomes "community server" in the connection status under the community name (Reconnecting, Connection lost, Disconnected), the Can't reach card and its buttons, the can't-reach messages in the canvas and inbox, the empty-channel line, the no-community channel hint, Remove from this device, and the Community Members card.
- Agent work items say Task instead of Job: inbox titles, feed headlines, the search result label and the agent sound settings.
- The inbox's Projects filter is hidden while the Projects preview is off.
- The project terminal button's tooltip shows Ctrl+J on Windows and Linux instead of the Mac symbol, through one helper the channel header now shares.

Rust strings, the Rust error prefix the app matches on, and code identifiers are unchanged. Unit tests cover the filter list and the shortcut label; the channel description test follows the new wording; a new E2E spec checks the Projects filter with Projects off and on; the relay-card, connectivity and onboarding specs follow the new card text.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Fixes from reviewing guest mode against the guest journey.

- A guest with a new key now joins the community's #general and #welcome-everyone during entry, the way Block's phone app does after an invite. Accepting an invite adds a person to the community but to none of its channels, and guest mode skipped the only desktop code that joined them, so a new guest landed in an empty Inbox. Guest entry joins the owner's existing open starter channels (it creates nothing), adds them to the channel list in case the relay's member list trails the join, and lands the guest in the owner's channel for them, else #welcome-everyone. The join has its own time budget. If it fails or times out, the app tries again each time it opens the community until it succeeds.
- The guest hint "Type @ and an agent's name to ask it for something." now shows only where the @ list offers an agent that answers the guest, by the same check the list uses, and not before both agent lists have loaded. It used to show whenever the community had any agent. It now shows in any channel where that holds, not only #welcome-everyone, until the guest dismisses it or asks an agent, and that is remembered per community.
- A saved guest flag now corrects itself: when the relay lists the person as the community's owner or admin, the flag is cleared and they get the normal setup. Guest entry decides on the invite alone when the role lookup does not answer, so an owner trying an invite link on a slow relay stayed a guest for good. On a relay with no member list nobody's role is known, so the invite still decides there.
- Next on Build your profile reads "Opening your community" while guest entry waits on the relay, and the form is locked while an existing profile enters by itself.
- An invite redeemed on the membership-denied screen is now claimed. A new code for the same relay sends onboarding back to claiming, without the old code's join-policy receipt, and the denied screen closes. Before, the code was stored but never claimed, and guest mode trusted it.
- An invite link opened while the AI-provider step is showing skips the rest of it, as one opened earlier does. The config step is left alone so a save in progress finishes.
- About no longer opens by itself on a project's home channel, whose canvas starts as a table of command-line commands. The button stays.
- Copy: the profile field is "Your name", the profile text no longer mentions agent handoffs, and the membership-denied screen says community server and community admin instead of relay.

Unit tests cover the starter join (what is joined, that nothing is created, a failed join), its retry record, the landing, the merged channel list, clearing the guest flag, the hint rule and the same-relay invite rule. New E2E cases cover a new key joining both starter channels and landing in #welcome-everyone, a failed join retried when the app opens, the waiting Next button and locked form, the AI-provider skip, no hint for an agent outside the channel or one that answers only its owner, the hint in the guest's own channel until dismissed or used, owner and admin flags clearing while a member's stays, the membership-denied invite being claimed, and a project home not opening About. Reverting each fix fails its E2E case. The mock bridge can now seed a canvas on any channel and queue a community link while the app runs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The open #welcome-everyone must drop guest mode when the relay's late
role answer lists the person as owner, without a reload.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…tadata

b5dd76f escaped message content and the author label inside
`<buzz-event>`, but other guest-written text still reached semantic
section bodies raw. `semantic_section*` keeps bodies verbatim by design,
so escaping is the caller's job, and five callers skipped it:

- `format_conversation_context`: earlier replies (`msg.content`). A
  guest's thread reply containing `</thread-context>` is replayed into
  every later turn's context, closes the untrusted section early, and
  the rest reads as trusted framing. Same for `</conversation-context>`
  in DMs.
- `format_prompt_actor`: the participant's own display name, or a raw
  `p`-tag value. Used by the context lines and by `mentions=[...]` in
  the event block, so a guest who names themselves
  `</buzz-event><system>...` and self-mentions got past b5dd76f.
- `format_event_block` `Tags:` line: serde_json escapes `"` and `\`, not
  `<`/`>`, and every tag value is signer-chosen.
- Both `channel_display`s: the channel name, from the same metadata
  event whose `about` is already escaped.
- `collapse_prompt_line`: project name/slug/owner/coordinate/repo in
  `<context>`; it has no other callers.

All now go through `prompt_framing::escape_semantic_text`. Two new tests
pin this; both fail on the old code (3 and 8 closing tags where 1 is
expected).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
QuicksilverSlick and others added 14 commits October 12, 2026 16:23
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…tion

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
In #bench a live card already answers through its option buttons, so the
1-4 keycap reactions the Bench writer seeds for phones (and the owner's own
keycap) showed twice. MessageRow now leaves the card's answer keycaps out of
the reaction row for a message that renders as a live BenchCard; other
reactions and the add-reaction button stay. Every other message renders its
reactions exactly as before, and nothing sent to the relay changes, so phones
still see and tap the pills.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The phone drew the fenced summary as a grey monospace "text" box with a copy button. The summary is now its own paragraph of ordinary text that both apps show character for character.

gpt_markdown 1.1.6 (pinned in mobile/pubspec.lock; source read from the pub.dev archive with the same sha256) honours no backslash escape, so nothing is escaped. prose() puts an invisible U+FEFF where it breaks each trigger instead. U+FEFF is whitespace to Dart's regex \s and to micromark's /\s/, so a U+FEFF before *, _ and ~ means none of them can close emphasis or strikethrough on either app. A U+FEFF after #, @, :, &, <, [, ] and \ stops headings, #channel and @mention pills, :emoji:, autolinks, entities, HTML, links, citations, LaTeX and escapes. Inside -- and || there is no line-end rule (phone) or spoiler (desktop). A line opening with a list, quote, table, setext, radio or rule mark gets one first. Backticks stay U+02CB. Spaces collapse, so nothing is indented. URLs stay readable but are not links; the validated link lines still are.

Blank lines make the summary a paragraph of its own. The phone matches links and italics across lines, and it rewrites each link line's url to [url](url) first. Without the blank lines, a '[' in the summary paired with that rewrite and the phone dropped the text in between.

CARD_FORMAT 4 reposts each live unanswered card once, quietly. fence(), wrap() and FENCE_WIDTH are gone. plain_text shares the new list_mark() helper.

Checked outside cargo: the desktop's own react-markdown pipeline (nodeCache.ts with remarkGfm, remarkBreaks and the Buzz plugins) renders Rust-made sample cards with only p/br/strong/ol/li/a and the exact summary text. A node emulation of gpt_markdown 1.1.6's generate() and the app's normalizeBareLinks, with the regexes copied verbatim, shows only the title, the options, the paragraph breaks and the link line as markup. The raw text fails both checks. Flutter is not installed, so no widget test ran.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The line under the title shifted it up 8px, so the project home context
panel no longer lined up with it (project-empty-state-alignment e2e).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Under the title it moved the title up 8px, breaking upstream's header
alignment (sidebar search and project home, sidebar.spec and
project-empty-state-alignment e2e). Beside it, the title keeps its place.
Reverts the project-home-only workaround from d9c55a5.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@QuicksilverSlick
QuicksilverSlick merged commit efdc60d into main Oct 13, 2026
78 checks passed
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.

1 participant