You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Chinese posts show stray spaces mid-sentence: hard-wrapped zh-Hans sources render each line break between CJK characters as a visible space (94 places in 4 posts, plus their zh-Hant) #225
Found by the #209 post sweep (staged on 2026-10-06) and counted by the seat on a production build of 53764e0 with the sweep applied. Priority: P1. It is visible in running text, e.g. 「已经在运转,数据 也已经在那里。」
What happens
Some zh-Hans post sources are hard-wrapped. The resulting newline between two CJK characters reaches the HTML as a segment break. Chromium (and Safari) render it as a space; CSS Text 3 says it should be removed between CJK characters, but those engines do not do that yet.
Affected zh-Hans posts, with the occurrence count outside code: crm-ai-understands-customers (20), extend-existing-systems-with-ai (36), low-code-vs-ai-native-app-platform (22), manufacturing-legacy-systems-ai (16). Their zh-Hant versions are generated from these sources, so they show the same spaces.
Done looks like
Fix: no visible space where a source line break falls between two CJK characters (or CJK punctuation), in any zh-Hans or zh-Hant post. Choose one approach and give the reason in the PR:
(a) a build-time remark/rehype step that removes a segment break between two CJK characters (code and pre excluded);
(b) reflow the four sources to one paragraph per line, then regenerate zh-Hant.
(a) also protects future posts.
Guard: a content-lint rule that fails on a hard-wrapped CJK paragraph in source (if you choose b), or on a remaining CJK segment break in dist (if you choose a).
Browser check: screenshots of the passages in the 4 posts, before and after, at 1440 and 360, in zh-Hans and zh-Hant.
Claim: PM loop, repo:www.objectos.ai seat
Session: session_01FeA1nwBz1ohH65dvffUGKr
Account: hotlong (the seat's linked user, as GET /user answers it; always the card's assignee)
Branch: claude/pm-dispatch-objectos-ju9td1, the session's one designated push branch for this repo. It carries PR #212 until its Thu 2026-10-08 slot. After that, wave 1 goes in this order: #216, #213, #217, #221, then the #209 post sweep. All five are staged and reviewed.
This card is staged in wave 2. Its work is built and verified on a local branch cut from the local integration branch stack/www-wave1 (origin/main with the five wave-1 branches merged in queue order, no conflicts). At push time only this card's own commits are replayed onto origin/main (rebase --onto).
Worktree: www-issue-225
Domain: n/a (single-lane repo)
Seat: repo:www.objectos.ai (objectstack-ai/objectstack#12224)
File surface: as the card's Surface section names. Overlaps with the other wave-2 cards (#218, #219, #224, #225) stay in separate hunks.
Stop on breach and explain in the report.
Container & model: M, mode:subagent, model: default.
Clause-②: no
Thread-read: none
Serial constraints cleared: no. Pushes are serial on the one branch, in the queue order on #2096010716407, with wave 1 first.
{
"issue": 225,
"status": "staged",
"branch": "staged/issue-225",
"worktree": "/home/user/www-issue-225",
"base": "7f69d97 (local stack/www-wave1: origin/main + staged #216, #213, #217, #221, #209-sweep)",
"final_commit": "9b91a79",
"commits": [
"9b91a79 fix(blog): remove the space a hard-wrapped line leaves between two CJK characters"
],
"pr": null,
"pr_body_draft": "/tmp/claude-0/-home-user/2224823d-3484-535e-94a5-c071ba6af395/scratchpad/issue-225/pr-body.md (first line: Fixes #225)",
"session": "session_01FeA1nwBz1ohH65dvffUGKr — this run's harness-stamped id (subagent = parent's)",
"premise_still_valid": true,
"summary": "Approach (a). A rehype step, rehypeCjkSegmentBreaks (scripts/lib/cjk-segment-break.mjs), is wired into markdown.rehypePlugins in astro.config.mjs, and MDX inherits it. It removes a segment break when both neighbours are East Asian Wide, Fullwidth or Halfwidth and neither is Hangul, which is the CSS Text 3 rule. Code, pre, kbd and samp are opaque. A break between CJK and Latin, after an ambiguous-width dash or curly quote, or between Hangul words stays a space. Breaks across inline elements (strong, a) are handled. Reason for (a) over (b): it covers future posts and ja, leaves the sources as written, and avoids a reflow on top of the wave-1 edits to these files. ko is excluded on purpose, because a break between Hangul words is a real space. Guard: content:lint --dist gains checkBuiltCjkBreaks, which scans every built page with its own HTML scanner. It is red on the stack/www-wave1 build (202 breaks on 8 pages) and green at 9b91a79 (0 of 880 pages, 333 zh/ja pages). A build with no zh or ja page fails as unmeasured. Premise reproduced; the count differs from the card's: 101 per locale here vs the card's 94 (see deviations). Committed locally only: not pushed, no PR, worktree kept.",
"files": [
"scripts/lib/cjk-segment-break.mjs (new: shared character and element definitions, collapseCjkSegmentBreaks, rehypeCjkSegmentBreaks)",
"astro.config.mjs (two hunks: an import after the mdx import; a markdown.rehypePlugins line after server. The sitemap integration is not touched)",
"scripts/content-lint.mjs (import, plus the new --dist check checkBuiltCjkBreaks and its proseRuns scanner; the --dist exit now combines three checks)"
],
"measurements": {
"method": "Built HTML. A CJK-pair segment break is a newline run (with the spaces and tabs around it) in inline prose, with code and pre excluded, where both neighbours are wide CJK and not Hangul. Three readings agree: an independent scratch scanner, the guard, and an in-browser Chromium Range probe of each break's rendered width.",
"zh-Hans before→after": "crm-ai-understands-customers 20→0, extend-existing-systems-with-ai 40→0, low-code-vs-ai-native-app-platform 23→0, manufacturing-legacy-systems-ai 18→0 (101→0)",
"zh-Hant before→after": "the same four posts: 20→0, 40→0, 23→0, 18→0 (101→0)",
"ja": "0→0. Sources are not hard-wrapped; a probe shows a hard-wrapped ja line collapses.",
"ko": "0 Hangul-pair breaks exist. Hangul is excluded by design.",
"rendered width (Chromium)": "Before at 1440: all 101 breaks per locale render about 4.72px wide. Before at 360: 4.0 to 4.45px mean; 0 to 2 breaks per post fall at a line end and render 0px. After: 0 breaks remain to render.",
"latin unchanged": "Whole-site byte diff, stack/www-wave1 build vs the 9b91a79 build: 1073 of 1081 files are byte-identical, including every en, de, es, fr, ja and ko page. Only the 4 posts in zh-Hans and zh-Hant differ. For each of those 8 files, after === before with exactly the CJK-pair newlines removed (true for all 8; 20/40/23/18 bytes each). All other break classes keep identical counts: latin|latin 369, cjk|latin 14, latin|cjk 10, ambig|cjk 6, cjk|none 16."
},
"guard": {
"red": "dist from stack/www-wave1 (7f69d97): node scripts/content-lint.mjs --dist exits 1 with 'content lint --dist failed (202 segment breaks between CJK characters on 8 pages; ⏎ marks each)'",
"green": "at 9b91a79: '✓ content lint --dist CJK line breaks passed (880 built pages, 333 in zh/ja, 0 segment breaks between CJK characters)'",
"unmeasured": "A dist holding one en page exits 1 with 'found no zh or ja page … among 1 built page'.",
"scanner probe": "A synthetic zh page with 10 cases flags exactly the 3 expected ones (plain, across strong, across a link). Code, pre, CJK and Latin, Hangul, the ambiguous dash, and SVG title/text are not flagged."
},
"gates": [
"pnpm check: CHECK_EXIT=0. 'Result (144 files): 0 errors, 0 warnings, 0 hints'",
"pnpm build: BUILD_EXIT=0. '[build] Complete!'; inside the build: '✓ zh-Hant: generated 46, kept 0 hand-maintained', '✓ content lint passed (339 files, 44 glossary terms checked)', 'svg-text-fit: OK', and the three --dist checks pass. The tree was clean after zh-Hant regeneration.",
"pnpm content:lint: EXIT=0. '✓ content lint passed (339 files, 44 glossary terms checked)'",
"pnpm content:lint --published: EXIT=0. '✓ content lint passed (339 files, 44 glossary terms checked)'",
"pnpm content:lint --dist: EXIT=0. '✓ content lint --dist passed (478 built blog pages checked)' / '✓ content lint --dist CJK line breaks passed (880 built pages, 333 in zh/ja, 0 segment breaks between CJK characters)' / '✓ content lint --dist links passed (89567 internal links in 880 built pages resolve to built files)'",
"pnpm seo:smoke: EXIT=0. 'SEO smoke test passed (870 HTML pages checked)'",
"pnpm svg:lint: EXIT=0. 'svg-text-fit: OK … (112 SVGs, 2244 text boxes, tolerance 2px, 1 allowlisted)'",
"lock: check and build ran under os-verify-lock. 'VERDICT batch-last-exit 0' (the two parts were joined with ';', so each part's exit was captured separately, as above). The baseline and after builds each read 'VERDICT command-exit 0'.",
"All gates ran at HEAD 9b91a79."
],
"tests": "No test runner exists in this repo, so the proofs are one-off probes and nothing permanent was added. (1) The plugin, through @astrojs/markdown-remark createMarkdownProcessor, on 13 cases. Removed: CJK pair, across strong, across a link, ja, list continuation. Kept: CJK and Latin, Latin and Latin, next to inline code, Hangul, after the dash. Untouched: inside inline code, fenced code, hard break. (2) The guard: red, green and unmeasured, plus the synthetic scanner probe, as above. (3) The whole-site byte diff and per-file reconstruction, as above. (4) The Chromium Range-width probe.",
"screenshots": "/tmp/claude-0/-home-user/2224823d-3484-535e-94a5-c071ba6af395/scratchpad/issue-225/shots/: 16 labelled sheets (sheet.SLUG.LOCALE.WIDTH.png, before over after) and 32 single shots, covering 4 posts × zh-Hans/zh-Hant × 1440/360. All 16 sheets were looked at. Before, the gaps are visible (新平台、 重新实现 / 数据 也已经 / 哪些客户 快要流失 / 很多 内部工具 / 先把系统 重构一遍). After, they are gone, and —— CRM, AI 的, 还在 Excel keep their spaces.",
"deviations": [
"The count differs from the card. The card says 94 (20/36/22/16) on 53764e0 with the sweep applied. This branch measures 101 per locale (20/40/23/18) on stack/www-wave1, which also has the other wave-1 edits. The card's counting method is not recorded, so the gap is reported, not explained.",
"The guard scans every built HTML page, not only blog pages. This is broader than the card's minimum. It is green today, with 0 non-post hits on the baseline.",
"Two local commits were folded into one with reset --soft to the recorded base 7f69d97 before reporting. Nothing had been pushed.",
"The two static servers used for screenshots were started by this run and stopped by their PIDs (13286, 13287)."
],
"mcp_calls": "0",
"api_writes": "1 — POST repos/objectstack-ai/www.objectos.ai/issues/225/comments (this os-dev-report, via scripts/pm/post-stamped.mjs)",
"open_questions": [],
"out_of_scope_findings": [
"class: a · reach: named real producer: pnpm wechat / pnpm wechat:draft (scripts/lib/wechat-html.mjs toWechatHtml, rendered by marked, not by Astro, so this step does not reach it) · evidence: the export HTML for the four zh-Hans sources still has 20/48/23/18 CJK-pair segment breaks outside code (measured with the shared isCjkWide). It is the same defect family as #225; a likely fix is to apply collapseCjkSegmentBreaks to marked text tokens. Not fixed in place, because there is no gate on the export output (a new verification surface) · dedupe words: wechat export, marked, CJK line break, stray space, 公众号",
"class: a · reach: public door: /zh-Hans/blog/extend-existing-systems-with-ai/ renders 「大多数”给你的业务加上 AI”的说辞」 with a closing quote where an opening one belongs (visible in the screenshot sheets) · evidence: smartypants (Astro default) makes an ASCII double quote after a CJK character a closing quote. A heuristic count (two closing quotes with no opening quote between, outside code) finds 303 pairs in 16 zh-Hans posts, 303 in zh-Hant and 12 in ko · dedupe words: smartypants, curly quotes, quote direction, CJK, 引号方向"
]
}
Checks: PR CI Build and deploy was green on the head. On main, run 38734905754 Build and deploy succeeded on 49580ae, with check, content lint, build, SEO smoke and the Pages deploy.
Housekeeping: the seat removes pm:dispatched here.
Activity
objectstack-fleet commentedon Oct 6, 2026
Claim: PM loop,
repo:www.objectos.aiseatSession:
session_01FeA1nwBz1ohH65dvffUGKrAccount:
hotlong(the seat's linked user, asGET /useranswers it; always the card's assignee)Branch:
claude/pm-dispatch-objectos-ju9td1, the session's one designated push branch for this repo. It carries PR #212 until its Thu 2026-10-08 slot. After that, wave 1 goes in this order: #216, #213, #217, #221, then the #209 post sweep. All five are staged and reviewed.This card is staged in wave 2. Its work is built and verified on a local branch cut from the local integration branch
stack/www-wave1(origin/mainwith the five wave-1 branches merged in queue order, no conflicts). At push time only this card's own commits are replayed ontoorigin/main(rebase --onto).Worktree:
www-issue-225Domain: n/a (single-lane repo)
Seat:
repo:www.objectos.ai(objectstack-ai/objectstack#12224)File surface: as the card's Surface section names. Overlaps with the other wave-2 cards (#218, #219, #224, #225) stay in separate hunks.
Stop on breach and explain in the report.
Container & model: M,
mode:subagent,model: default.Clause-②: no
Thread-read: none
Serial constraints cleared: no. Pushes are serial on the one branch, in the queue order on #209
6010716407, with wave 1 first.Generated by Claude Code
objectstack-fleet commentedon Oct 6, 2026
os-dev-report
{ "issue": 225, "status": "staged", "branch": "staged/issue-225", "worktree": "/home/user/www-issue-225", "base": "7f69d97 (local stack/www-wave1: origin/main + staged #216, #213, #217, #221, #209-sweep)", "final_commit": "9b91a79", "commits": [ "9b91a79 fix(blog): remove the space a hard-wrapped line leaves between two CJK characters" ], "pr": null, "pr_body_draft": "/tmp/claude-0/-home-user/2224823d-3484-535e-94a5-c071ba6af395/scratchpad/issue-225/pr-body.md (first line: Fixes #225)", "session": "session_01FeA1nwBz1ohH65dvffUGKr — this run's harness-stamped id (subagent = parent's)", "premise_still_valid": true, "summary": "Approach (a). A rehype step, rehypeCjkSegmentBreaks (scripts/lib/cjk-segment-break.mjs), is wired into markdown.rehypePlugins in astro.config.mjs, and MDX inherits it. It removes a segment break when both neighbours are East Asian Wide, Fullwidth or Halfwidth and neither is Hangul, which is the CSS Text 3 rule. Code, pre, kbd and samp are opaque. A break between CJK and Latin, after an ambiguous-width dash or curly quote, or between Hangul words stays a space. Breaks across inline elements (strong, a) are handled. Reason for (a) over (b): it covers future posts and ja, leaves the sources as written, and avoids a reflow on top of the wave-1 edits to these files. ko is excluded on purpose, because a break between Hangul words is a real space. Guard: content:lint --dist gains checkBuiltCjkBreaks, which scans every built page with its own HTML scanner. It is red on the stack/www-wave1 build (202 breaks on 8 pages) and green at 9b91a79 (0 of 880 pages, 333 zh/ja pages). A build with no zh or ja page fails as unmeasured. Premise reproduced; the count differs from the card's: 101 per locale here vs the card's 94 (see deviations). Committed locally only: not pushed, no PR, worktree kept.", "files": [ "scripts/lib/cjk-segment-break.mjs (new: shared character and element definitions, collapseCjkSegmentBreaks, rehypeCjkSegmentBreaks)", "astro.config.mjs (two hunks: an import after the mdx import; a markdown.rehypePlugins line after server. The sitemap integration is not touched)", "scripts/content-lint.mjs (import, plus the new --dist check checkBuiltCjkBreaks and its proseRuns scanner; the --dist exit now combines three checks)" ], "measurements": { "method": "Built HTML. A CJK-pair segment break is a newline run (with the spaces and tabs around it) in inline prose, with code and pre excluded, where both neighbours are wide CJK and not Hangul. Three readings agree: an independent scratch scanner, the guard, and an in-browser Chromium Range probe of each break's rendered width.", "zh-Hans before→after": "crm-ai-understands-customers 20→0, extend-existing-systems-with-ai 40→0, low-code-vs-ai-native-app-platform 23→0, manufacturing-legacy-systems-ai 18→0 (101→0)", "zh-Hant before→after": "the same four posts: 20→0, 40→0, 23→0, 18→0 (101→0)", "ja": "0→0. Sources are not hard-wrapped; a probe shows a hard-wrapped ja line collapses.", "ko": "0 Hangul-pair breaks exist. Hangul is excluded by design.", "rendered width (Chromium)": "Before at 1440: all 101 breaks per locale render about 4.72px wide. Before at 360: 4.0 to 4.45px mean; 0 to 2 breaks per post fall at a line end and render 0px. After: 0 breaks remain to render.", "latin unchanged": "Whole-site byte diff, stack/www-wave1 build vs the 9b91a79 build: 1073 of 1081 files are byte-identical, including every en, de, es, fr, ja and ko page. Only the 4 posts in zh-Hans and zh-Hant differ. For each of those 8 files, after === before with exactly the CJK-pair newlines removed (true for all 8; 20/40/23/18 bytes each). All other break classes keep identical counts: latin|latin 369, cjk|latin 14, latin|cjk 10, ambig|cjk 6, cjk|none 16." }, "guard": { "red": "dist from stack/www-wave1 (7f69d97): node scripts/content-lint.mjs --dist exits 1 with 'content lint --dist failed (202 segment breaks between CJK characters on 8 pages; ⏎ marks each)'", "green": "at 9b91a79: '✓ content lint --dist CJK line breaks passed (880 built pages, 333 in zh/ja, 0 segment breaks between CJK characters)'", "unmeasured": "A dist holding one en page exits 1 with 'found no zh or ja page … among 1 built page'.", "scanner probe": "A synthetic zh page with 10 cases flags exactly the 3 expected ones (plain, across strong, across a link). Code, pre, CJK and Latin, Hangul, the ambiguous dash, and SVG title/text are not flagged." }, "gates": [ "pnpm check: CHECK_EXIT=0. 'Result (144 files): 0 errors, 0 warnings, 0 hints'", "pnpm build: BUILD_EXIT=0. '[build] Complete!'; inside the build: '✓ zh-Hant: generated 46, kept 0 hand-maintained', '✓ content lint passed (339 files, 44 glossary terms checked)', 'svg-text-fit: OK', and the three --dist checks pass. The tree was clean after zh-Hant regeneration.", "pnpm content:lint: EXIT=0. '✓ content lint passed (339 files, 44 glossary terms checked)'", "pnpm content:lint --published: EXIT=0. '✓ content lint passed (339 files, 44 glossary terms checked)'", "pnpm content:lint --dist: EXIT=0. '✓ content lint --dist passed (478 built blog pages checked)' / '✓ content lint --dist CJK line breaks passed (880 built pages, 333 in zh/ja, 0 segment breaks between CJK characters)' / '✓ content lint --dist links passed (89567 internal links in 880 built pages resolve to built files)'", "pnpm seo:smoke: EXIT=0. 'SEO smoke test passed (870 HTML pages checked)'", "pnpm svg:lint: EXIT=0. 'svg-text-fit: OK … (112 SVGs, 2244 text boxes, tolerance 2px, 1 allowlisted)'", "lock: check and build ran under os-verify-lock. 'VERDICT batch-last-exit 0' (the two parts were joined with ';', so each part's exit was captured separately, as above). The baseline and after builds each read 'VERDICT command-exit 0'.", "All gates ran at HEAD 9b91a79." ], "tests": "No test runner exists in this repo, so the proofs are one-off probes and nothing permanent was added. (1) The plugin, through @astrojs/markdown-remark createMarkdownProcessor, on 13 cases. Removed: CJK pair, across strong, across a link, ja, list continuation. Kept: CJK and Latin, Latin and Latin, next to inline code, Hangul, after the dash. Untouched: inside inline code, fenced code, hard break. (2) The guard: red, green and unmeasured, plus the synthetic scanner probe, as above. (3) The whole-site byte diff and per-file reconstruction, as above. (4) The Chromium Range-width probe.", "screenshots": "/tmp/claude-0/-home-user/2224823d-3484-535e-94a5-c071ba6af395/scratchpad/issue-225/shots/: 16 labelled sheets (sheet.SLUG.LOCALE.WIDTH.png, before over after) and 32 single shots, covering 4 posts × zh-Hans/zh-Hant × 1440/360. All 16 sheets were looked at. Before, the gaps are visible (新平台、 重新实现 / 数据 也已经 / 哪些客户 快要流失 / 很多 内部工具 / 先把系统 重构一遍). After, they are gone, and —— CRM, AI 的, 还在 Excel keep their spaces.", "deviations": [ "The count differs from the card. The card says 94 (20/36/22/16) on 53764e0 with the sweep applied. This branch measures 101 per locale (20/40/23/18) on stack/www-wave1, which also has the other wave-1 edits. The card's counting method is not recorded, so the gap is reported, not explained.", "The guard scans every built HTML page, not only blog pages. This is broader than the card's minimum. It is green today, with 0 non-post hits on the baseline.", "Two local commits were folded into one with reset --soft to the recorded base 7f69d97 before reporting. Nothing had been pushed.", "The two static servers used for screenshots were started by this run and stopped by their PIDs (13286, 13287)." ], "mcp_calls": "0", "api_writes": "1 — POST repos/objectstack-ai/www.objectos.ai/issues/225/comments (this os-dev-report, via scripts/pm/post-stamped.mjs)", "open_questions": [], "out_of_scope_findings": [ "class: a · reach: named real producer: pnpm wechat / pnpm wechat:draft (scripts/lib/wechat-html.mjs toWechatHtml, rendered by marked, not by Astro, so this step does not reach it) · evidence: the export HTML for the four zh-Hans sources still has 20/48/23/18 CJK-pair segment breaks outside code (measured with the shared isCjkWide). It is the same defect family as #225; a likely fix is to apply collapseCjkSegmentBreaks to marked text tokens. Not fixed in place, because there is no gate on the export output (a new verification surface) · dedupe words: wechat export, marked, CJK line break, stray space, 公众号", "class: a · reach: public door: /zh-Hans/blog/extend-existing-systems-with-ai/ renders 「大多数”给你的业务加上 AI”的说辞」 with a closing quote where an opening one belongs (visible in the screenshot sheets) · evidence: smartypants (Astro default) makes an ASCII double quote after a CJK character a closing quote. A heuristic count (two closing quotes with no opening quote between, outside code) finds 303 pairs in 16 zh-Hans posts, 303 in zh-Hant and 12 in ko · dedupe words: smartypants, curly quotes, quote direction, CJK, 引号方向" ] }objectstack-fleet commentedon Oct 15, 2026
Landed and live · 2026-10-15T12:30Z
49580ae, merged by the seat. It was replayed ontomainfrom the seat's linear stack, patch-equal to the staged branch the seat reviewed.Build and deploywas green on the head. Onmain, run 38734905754Build and deploysucceeded on49580ae, with check, content lint, build, SEO smoke and the Pages deploy.pm:dispatchedhere.Generated by Claude Code