Skip to content

fix(composer): preserve UTF-8-safe excerpts - #106

Merged
withally merged 2 commits into
mainfrom
fm/fm-composer-excerpt-utf8-cap-f2
Sep 2, 2026
Merged

withally merged 2 commits into
mainfrom
fm/fm-composer-excerpt-utf8-cap-f2

Conversation

@withally

@withally withally commented Sep 2, 2026

Copy link
Copy Markdown
Owner

Intent

Implement a UTF-8-safe 80-character cap for the preserved composer excerpt in the shared owner bin/fm-composer-lib.sh. The current cut -c 1-80 runs under LC_ALL=C, truncates at 80 bytes, and can split a UTF-8 code point, making the saved excerpt not reliably resendable. Keep the implementation portable to Bash 3.2 on macOS and Linux with no new dependencies. Add an executable behavior regression covering 79 ASCII characters followed by a multibyte character under LC_ALL=C and under a UTF-8 locale. All touched tests must pass through bin/fm-test-run.sh. Keep this to one fix in one PR and no more than two review rounds.

What Changed

  • Replace the byte-based excerpt truncation with a Bash 3.2-compatible UTF-8 scanner that preserves up to 80 complete code points.
  • Add an executable regression for 79 ASCII characters followed by a multibyte character under LC_ALL=C and a UTF-8 locale.
  • Document the UTF-8-safe excerpt behavior in the agent-control guide.

Risk Assessment

✅ Low: The bounded shared-helper change preserves complete valid UTF-8 code points under C while retaining the existing excerpt output contract.

Testing

Darwin Bash 3.2.57 reproduced the base split-code-point failure; the target preserved the complete UTF-8 character in both locale classes. The focused runner exited 0 with no gate skips, and the worktree remained clean. No linters or full-suite tests were run per the assigned phase boundary.

Evidence: final focused fm-test-run output

Source: final focused fm-test-run output

FM_TEST_BEGIN 2026-09-02T23:30:28Z tests/fm-composer-lib.test.sh family=pure-contract-unit expected_gate_skip=none
ok - fm_composer_state_output: the 80-character excerpt cap is UTF-8-safe in C and UTF-8 locales
ok - fm_composer_classify_content: a bare shell prompt glyph (>/$/%/#) reads unknown, never empty
ok - fm_composer_classify_content: stripped unbordered content is unknown except verified agent glyphs
ok - fm_composer_classify_content: a bare shell prompt carrying a command is not empty
ok - fm_composer_classify_content: a bare prompt glyph inside a bordered composer box reads empty (claude's own idle composer)
ok - fm_composer_classify_content: agent prompt glyphs (❯ claude, › codex, ⟩ muse) read empty bordered or bare
ok - fm_composer_classify_content: an empty composer reads empty
ok - fm_composer_classify_content: idle matching is limited to proven placeholder positions
ok - fm_composer_classify_content: idle matching preserves the caller's case mode
ok - fm_composer_classify_content: real unsubmitted text reads pending (including a popup argument-hint fill)
ok - matrix: claude's ❯+NBSP row reads empty on every profile in both locales (#1988)
ok - matrix: codex's dim hint is empty when styling proves it, unknown (never pending) when it cannot
ok - matrix: muse's ⟩ reads empty everywhere and survives losing the styled-glyph signal
ok - matrix: cursor's reverse-video placeholder remnant reads empty; real typed text stays pending
ok - matrix: herdr half-block rules bound a bare composer's wrap region
ok - matrix: pi's separated composer needs identity + structure; the blank row alone never proves it
ok - matrix: opencode's left-bar composer reads empty everywhere and scans the full active run
ok - matrix: grok's titled bottom border is tolerated as a title, not read as ambiguity
ok - matrix: kimi's bordered shell-glyph box reads empty through the shared owner (spawn's fourth copy retired)
ok - matrix: the real claude-in-zellij --ansi dump reads empty in both locales
ok - strict posture: blank and unidentified rows are unknown, never injectable empty
ok - fm_composer_classify_screen: the bare composer's wrap region stays identified; structure breaks it
ok - fm_composer_classify_screen: a row-leading agent glyph reanchors the live composer
ok - fm_composer_classify_screen: a lower dead shell invalidates only cursorless stale composers
ok - fm_composer_classify_screen: cursorless bare wrap regions participate in verdicts
ok - fm_composer_classify_screen: cursorless containers reject only contiguous unclaimed activity
ok - fm_composer_classify_screen: the bottom-most candidate wins; stale banners cannot
ok - fm_composer_classify_screen: incomplete lower structure invalidates stale boxes
ok - fm_composer_classify_screen: titled bottoms retain full box geometry
ok - fm_composer_classify_screen: a proven box tolerates a bottom-border cursor
ok - fm_composer_extract_selected_content: scopes user content and excludes furniture
ok - fm_composer_pi_submit_observation: over-tall pairs remain unknown
ok - fm_composer_pi_submit_observation: a different visible Pi draft is not the current pending literal
ok - fm_claude_current_footer_busy: the footer belongs to the selected composer boundary
ok - fm_claude_current_footer_busy: incident-B footer and ghost variants stay idle while genuine current turns stay busy
ok - fm_claude_current_footer_busy: active context distinguishes a live turn while idle and pending composers stay injectable-safe
ok - fm_claude_current_footer_busy: a plain capture carries its real capability into active-context classification
ok - fm_composer_submit_retry_core: failed Enter transport never claims an unconfirmed submit
ok - fm_composer_queued_enter_verdict: pending + busy returns empty (queued Enter)
ok - fm_composer_queued_enter_verdict: pending + idle/unknown stays pending
ok - fm_composer_queued_enter_verdict: only proven pending is converted
FM_TEST_END 2026-09-02T23:30:34Z tests/fm-composer-lib.test.sh exit=0 duration_ms=5796 gate_skip=false
FM_TEST_SUMMARY total=1 failed=0 skipped_gate=0 duration_ms=5846
FM_TEST_SUMMARY_FAMILY family=pure-contract-unit count=1 duration_ms=5796 failed=0
FM_TEST_SLOWEST rank=1 script=tests/fm-composer-lib.test.sh duration_ms=5796

Pipeline

Updates from git push no-mistakes

✅ **intent** - passed

✅ No issues found.

✅ **Rebase** - passed

✅ No issues found.

✅ **Review** - passed

✅ No issues found.

✅ **Test** - passed

✅ No issues found.

  • bin/fm-test-run.sh tests/fm-composer-lib.test.sh
  • fm_composer_state_output boundary reproduction under LC_ALL=C and LC_ALL=C.UTF-8 against base and target
  • Direct 2-, 3-, and 4-byte UTF-8 boundary checks under LC_ALL=C
  • git status --short --untracked-files=all
✅ **Document** - passed

✅ No issues found.

✅ **Lint** - passed

✅ No issues found.

✅ **Push** - passed

✅ No issues found.

@withally
withally merged commit 1f9a7f5 into main Sep 2, 2026
13 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