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 print quotation marks the wrong way round: smartypants turns ASCII quotes after CJK text into closing quotes (≈300 pairs in 16 zh-Hans posts, plus zh-Hant and ko); the WeChat export keeps CJK line-break spaces #226
Found while #225 was staged on 2026-10-06. The seat measured it on the #225 build, which is stack/www-wave1 (main plus the five wave-1 branches) with #225 applied. Priority: P1. It shows in running text on most Chinese posts, for example 「大多数”给你的业务加上 AI”的说辞」, where the first mark should be an opening quote.
1. Quotation marks point the wrong way
Cause. Astro's default smartypants turns an ASCII " into a curly quote, choosing the direction from the character before it. After a CJK character it chooses a closing quote, so the opening mark of every pair comes out as ”.
Extent. Counted as a CJK character followed by ”…” outside code:
Locale
Posts
Pairs
zh-Hans
16
≈300
zh-Hant
same posts, generated from zh-Hans
≈300
ko
—
12 (dev's count)
Fix options. Either one is acceptable; state the reason for your choice.
(a) Turn smartypants off for CJK locales, or replace it with a CJK-aware quote step:
zh text gets 「」 or the correct “ ”;
the choice follows AGENTS.md / the zh style if one is recorded;
if none is recorded, keep the source's intent with correct direction.
(b) Normalise the sources to explicit typographic quotes, then regenerate zh-Hant.
Guard. Add a check that fails on a wrong-direction pair in built zh, zh-Hant and ko prose. Show it red before the fix and green after.
2. WeChat export keeps the CJK line-break spaces
Cause.pnpm wechat / wechat:draft (scripts/lib/wechat-html.mjs → toWechatHtml) renders through marked, not Astro. #225's rehype step therefore never runs on the export.
Extent. In the export HTML of the four hard-wrapped zh-Hans posts, the counts of CJK segment breaks are still 20, 48, 23 and 18.
Fix. Apply #225's collapseCjkSegmentBreaks (from scripts/lib/cjk-segment-break.mjs) to marked's text tokens. Then run one export to show the result.
Done looks like
Built zh-Hans, zh-Hant and ko posts have 0 wrong-direction pairs.
Latin-locale pages are byte-identical, or only intended quote changes appear.
The export of the four posts has 0 CJK segment breaks.
Screenshots of 3 posts in zh-Hans and zh-Hant at 1440 and 360, before and after.
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. Wave 1 (#216, #213, #217, #221, #209 sweep) lands first, after #212's Thursday slot, and wave 2 follows. This card is staged on top of #225's local branch staged/issue-225, because it reuses #225's scripts/lib/cjk-segment-break.mjs. At push time only this card's own commits are replayed (rebase --onto).
Worktree: www-issue-226
Domain: n/a (single-lane repo)
Seat: repo:www.objectos.ai (objectstack-ai/objectstack#12224)
File surface:
the quote step (astro markdown config and/or a rehype/remark plugin under scripts/lib/), or the zh-Hans/ko sources if option (b) is chosen;
{
"issue": 226,
"status": "staged",
"branch": "staged/issue-226",
"worktree": "/home/user/www-issue-226",
"base": "9b91a79 (local staged/issue-225 = stack/www-wave1 7f69d97 + #225's cjk-segment-break commit)",
"final_commit": "36115b3",
"commits": [
"abd8146 fix(blog): point quotation marks in zh, ja and ko prose the right way",
"36115b3 fix(wechat): join CJK line breaks and direct quotation marks in the export"
],
"pr": null,
"pr_body_draft": "/tmp/claude-0/-home-user/2224823d-3484-535e-94a5-c071ba6af395/scratchpad/issue-226/pr-body.md (first line: Fixes #226)",
"session": "session_01FeA1nwBz1ohH65dvffUGKr — this run's harness-stamped id (subagent = parent's)",
"premise_still_valid": true,
"summary": "Approach (a): a remark step. remarkCjkQuotes (new scripts/lib/cjk-quotes.mjs) is wired into markdown.remarkPlugins, and MDX inherits it. It runs after Astro's smartypants on zh-Hans, zh-Hant, ja and ko files only, chosen by file name. In each paragraph, heading or table cell it sets every double and single quotation mark by its position in its pair (the first opens, the next closes), whether the source typed it straight or curly. Apostrophes stay ’: after a Latin letter or digit, or an elided century such as Next '26. Code is opaque. Marks stay “ ” / ‘ ’, because AGENTS.md records no zh style and the sources type “ ” 354 times against 23 for 「」; 「」 is untouched. Reason for (a) over (b): it protects future posts and leaves the sources as written. (b) would edit 25 sources and regress on the next ASCII-quoted post. Root cause found in addition to the card's: remark-smartypants reads a document as one string, so a heading or table cell starting with a quote takes its direction from the previous block. That is the whole ko defect, and part of zh. Guard: content:lint --dist gains checkBuiltCjkQuotes. Its own HTML scanner reads every zh, ja and ko page and fails on a curly mark whose direction disagrees with its position, or a pair left open. It is red on the staged/issue-225 build (664 marks on 41 pages) and green at 36115b3 (438 pages, 4214 marks, 0). WeChat: toWechatHtml applies collapseCjkSegmentBreaks and directQuotes to marked's tokens through walkTokens. The 4 posts' breaks go 20/40/23/18 to 0, quotes are curly and correctly directed, and the en export is byte-identical. Latin pages are byte-identical (whole-site diff). Committed locally only: not pushed, no PR, worktree kept.",
"files": [
"scripts/lib/cjk-quotes.mjs (new): CJK_QUOTE_LOCALES, cjkLocaleOfPath, isApostrophe, directQuotes, hasCjkLetter, remarkCjkQuotes",
"astro.config.mjs: two hunks. One import line after #225's import. The markdown line gains remarkPlugins: [remarkCjkQuotes], with a 4-line comment. Sitemap and head are not touched.",
"scripts/content-lint.mjs: an import, plus the new --dist check checkBuiltCjkQuotes (quoteBlocks scanner and misdirectedQuotes). The --dist exit now combines four checks.",
"scripts/lib/wechat-html.mjs: imports, inline-run helpers and a walkTokens callback (fixCjkBlock); marked.parse gains walkTokens"
],
"measurements": {
"method": "Built HTML, every page whose html lang is zh, ja or ko. A block is a paragraph, heading or table cell, joined across inline elements, with code as a placeholder. A mark is wrong when its curly direction disagrees with its position in its pair, or when its pair is left open. The card's metric (a CJK character followed by ”…”) was also run with an independent scratch scanner.",
"before (staged/issue-225 build, 9b91a79)": "zh-Hans 324 marks on 16 posts (card metric: 299 pairs on 16 posts); zh-Hant 324 on 16 (299 pairs); ko 15 on 8 posts. The card's metric reads 0 for ko, because every ko case is a heading or table cell starting with ”. ja 1 on 1 (Next ‘26). Total 664 on 41 pages.",
"after (36115b3)": "0 on 438 zh/ja/ko pages (4214 curly marks read)",
"whole-site byte diff": "1081 vs 1081 files, with the same file set. 1040 are byte-identical: every en, de, es and fr page, every asset, sitemaps, rss and llms.txt. The 41 that differ are exactly the guard's 41 pages: zh-Hans 16, zh-Hant 16, ko 8, ja 1. 672 characters changed: ” to “ x641, “ to ” x14, ’ to ‘ x5, ‘ to ’ x4, plus 4 straight pairs (the HTML entity for a double quote, twice) turned into ”“. Every changed character is a quotation mark, and none is inside code, pre, script, style, title or a tag. Per-locale changes are zh-Hans 328, zh-Hant 328, ko 15, ja 1, which equals the guard's count plus the 4 straight pairs.",
"wechat export, 4 hard-wrapped zh-Hans posts": "CJK segment breaks, read by #225's own --dist scanner: crm-ai-understands-customers 20 to 0, extend-existing-systems-with-ai 40 to 0, low-code-vs-ai-native-app-platform 23 to 0, manufacturing-legacy-systems-ai 18 to 0. Quotes: the new guard over the export passes before (80 curly) and after (92 curly, 0 wrong). The 12 added are extend-existing's 6 straight pairs, now “ ”. A char-level diff before vs after is exactly 101 deleted newlines plus 6+6 entity-to-curly replacements. The en export of crm-ai-understands-customers is byte-identical."
},
"guard": {
"red": "dist of staged/issue-225 (9b91a79): node scripts/content-lint.mjs --dist exits 1 with '✗ content lint --dist failed (664 quotation marks pointing the wrong way or left open on 41 zh/ja/ko pages; ⟦ ⟧ marks each)'. Sample: 'an opening mark printed as ”: …大多数⟦”⟧给你的业务加上 AI”的说辞…'",
"green": "at 36115b3: '✓ content lint --dist CJK quotation marks passed (438 zh/ja/ko pages, 4214 curly marks, 0 pointing the wrong way or left open)'",
"unmeasured": "A build with no zh/ja/ko page, or with no curly mark on any of them, exits 1 as unmeasured.",
"scanner probe": "A synthetic zh page flags exactly the 6 expected hits: ”…” opening, an unclosed “, a lone ” counted once, a table cell starting with ”, a misdirected single, and ‘26. Code contents, title and script, an en page, 「」, a pair across a link and a pair across br are not flagged. A ko page flags the ” heading and does not flag ‘40~60% 절약’."
},
"gates": [
"pnpm build (under os-verify-lock, at 36115b3): 'os-verify-lock: VERDICT command-exit 0'. Inside: '✓ zh-Hant: generated 46, kept 0 hand-maintained', '✓ content lint passed (339 files, 44 glossary terms checked)', svg-text-fit OK, and all four --dist checks pass. The tree was clean afterwards.",
"pnpm check (same lock call as build): 'Result (145 files): 0 errors, 0 warnings, 0 hints'",
"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)' / '✓ … CJK line breaks passed (880 built pages, 333 in zh/ja, 0 segment breaks between CJK characters)' / '✓ … CJK quotation marks passed (438 zh/ja/ko pages, 4214 curly marks, 0 pointing the wrong way or left open)' / '✓ … 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)'",
"These cover the CI workflow's gates (check, content:lint --published, build, seo:smoke) plus svg:lint and plain content:lint. The baseline build also read 'VERDICT command-exit 0'.",
"Control-byte self-scan over the 4 changed files: no match"
],
"tests": "The repo has no test runner, so the proofs are one-off probes and nothing permanent was added. (1) Unit probe of directQuotes on 8 cases, including ASCII “”“” pairs, a single pair inside a double, ’26, ‘40~60% 절약’, ‘고객’, don’t / users’ / ’90s, and pairs across inline code. (2) A mock-mdast probe of remarkCjkQuotes: a ko file is fixed (heading, strong, table cell, inline code untouched, fenced code untouched), the same tree under index.mdx is untouched, and cjkLocaleOfPath returns null for index.mdx and index.de.mdx. (3) Guard red, green and probe, as above. (4) The whole-site byte diff with char-level classification, as above. (5) The export diff, as above. (6) The final dist at 36115b3 is sha256-identical, file for file, to the measured after-dist.",
"screenshots": "/tmp/claude-0/-home-user/2224823d-3484-535e-94a5-c071ba6af395/scratchpad/issue-226/shots/: 24 viewport shots (POST.LOCALE.WIDTH.before|after.png) of extend-existing-systems-with-ai, ai-wrote-your-app-dare-to-merge and enterprise-ontology-race-open-vs-closed, in zh-Hans and zh-Hant at 1440 and 360, with the changed paragraph outlined. Also 12 2x crops (crop2x.POST.LOCALE.before|after.png). I looked at them: before, 大多数”给 / 只證明”程式碼…”,不證明”它做的 / 問的是”誰會擁有業務語義層” / 哪些”裝置”; after, each opens with “. The layout is otherwise identical at both widths.",
"deviations": [
"File surface was kept as declared. The WeChat step gates quote direction on a block containing a CJK letter rather than on a locale parameter, so the callers to-wechat.mjs and wechat-draft.mjs were not edited. As a result, an English export stays byte-identical, and an English-only block inside a zh export keeps marked's straight quotes.",
"The card's export count does not reproduce: 48 for extend-existing-systems-with-ai. Measured with #225's own scanner it is 40, and the source has 39 CJK|CJK line pairs; #225's report also has 40. The card's method is not recorded.",
"Scope is broader than the card's minimum. ja is in the step and the guard (1 change: ‘26 to ’26). Single marks are handled and guarded too: 9 single marks changed across zh-Hans, zh-Hant, ko and ja. The guard reads every zh, ja and ko page, not only posts; it is green today with 0 non-post hits.",
"ko cause differs from the card. It is smartypants' cross-block reading, not CJK adjacency, which is why the card's metric reads 0 for ko. The 15 ko marks are headings and table cells.",
"Commit trailers use the model-free pair (Co-Authored-By: Claude, Claude-Session), following the agent contract and #225's precedent. The harness reminder asked for a model-named Co-Authored-By; reported here, not followed.",
"The Write tool materialised U+FFFC raw in cjk-quotes.mjs. Before the commit it was respelled as the backslash-uFFFC escape, and the build and diff were rerun at the final HEAD.",
"Process: a slow scratch analysis I started in the background was stopped with TaskStop and replaced with a linear check. Two static servers for the screenshots (PIDs 1699 and 1701, started by this run) were killed and confirmed gone."
],
"mcp_calls": "0",
"api_writes": "1 — POST repos/objectstack-ai/www.objectos.ai/issues/226/comments (this os-dev-report, via scripts/pm/post-stamped.mjs)",
"open_questions": [],
"out_of_scope_findings": [
"class: a · reach: public page /en/blog/forward-deployed-engineer-tools/ table cells render ”What exists in our business?” (opening mark printed as closing); /en/blog/enterprise-ontology-platform-comparison/ renders Next ‘26. Producer: Astro default remark-smartypants, which reads the document as one string, so a heading or cell starting with a quote takes the previous block's last letter. · evidence: running this card's rule over en/es/fr on the 36115b3 build finds en 16 marks on 7 pages, es 5 on 4, fr 1 on 1 (22 on 12). de is not measured (German „…“ needs its own rule). The fix shape is pinned: add the Latin locales to cjkLocaleOfPath (per-block pairing is also correct for English). It was left alone because this card requires byte-identical Latin pages. · dedupe words: smartypants, quotation mark direction, heading, table cell, Next '26, en es fr",
"carrier: 承接者:无 · noted, not filed — zh-Hans product pages rendered from .astro/.ts data (e.g. /zh-Hans/analytics/, /zh-Hans/permissions/) use straight ASCII quotes in CJK prose. They have no direction, so this is polish and only in the PR's Acceptance notes."
]
}
Checks: PR CI Build and deploy was green on the head. On main, run 38735498990 Build and deploy succeeded on 6308287, 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. Wave 1 (#216, #213, #217, #221, #209 sweep) lands first, after #212's Thursday slot, and wave 2 follows. This card is staged on top of #225's local branchstaged/issue-225, because it reuses #225'sscripts/lib/cjk-segment-break.mjs. At push time only this card's own commits are replayed (rebase --onto).Worktree:
www-issue-226Domain: n/a (single-lane repo)
Seat:
repo:www.objectos.ai(objectstack-ai/objectstack#12224)File surface:
scripts/lib/), or the zh-Hans/ko sources if option (b) is chosen;scripts/lib/wechat-html.mjs;scripts/content-lint.mjs.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, and this card follows 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.
Generated by Claude Code
objectstack-fleet commentedon Oct 6, 2026
os-dev-report
{ "issue": 226, "status": "staged", "branch": "staged/issue-226", "worktree": "/home/user/www-issue-226", "base": "9b91a79 (local staged/issue-225 = stack/www-wave1 7f69d97 + #225's cjk-segment-break commit)", "final_commit": "36115b3", "commits": [ "abd8146 fix(blog): point quotation marks in zh, ja and ko prose the right way", "36115b3 fix(wechat): join CJK line breaks and direct quotation marks in the export" ], "pr": null, "pr_body_draft": "/tmp/claude-0/-home-user/2224823d-3484-535e-94a5-c071ba6af395/scratchpad/issue-226/pr-body.md (first line: Fixes #226)", "session": "session_01FeA1nwBz1ohH65dvffUGKr — this run's harness-stamped id (subagent = parent's)", "premise_still_valid": true, "summary": "Approach (a): a remark step. remarkCjkQuotes (new scripts/lib/cjk-quotes.mjs) is wired into markdown.remarkPlugins, and MDX inherits it. It runs after Astro's smartypants on zh-Hans, zh-Hant, ja and ko files only, chosen by file name. In each paragraph, heading or table cell it sets every double and single quotation mark by its position in its pair (the first opens, the next closes), whether the source typed it straight or curly. Apostrophes stay ’: after a Latin letter or digit, or an elided century such as Next '26. Code is opaque. Marks stay “ ” / ‘ ’, because AGENTS.md records no zh style and the sources type “ ” 354 times against 23 for 「」; 「」 is untouched. Reason for (a) over (b): it protects future posts and leaves the sources as written. (b) would edit 25 sources and regress on the next ASCII-quoted post. Root cause found in addition to the card's: remark-smartypants reads a document as one string, so a heading or table cell starting with a quote takes its direction from the previous block. That is the whole ko defect, and part of zh. Guard: content:lint --dist gains checkBuiltCjkQuotes. Its own HTML scanner reads every zh, ja and ko page and fails on a curly mark whose direction disagrees with its position, or a pair left open. It is red on the staged/issue-225 build (664 marks on 41 pages) and green at 36115b3 (438 pages, 4214 marks, 0). WeChat: toWechatHtml applies collapseCjkSegmentBreaks and directQuotes to marked's tokens through walkTokens. The 4 posts' breaks go 20/40/23/18 to 0, quotes are curly and correctly directed, and the en export is byte-identical. Latin pages are byte-identical (whole-site diff). Committed locally only: not pushed, no PR, worktree kept.", "files": [ "scripts/lib/cjk-quotes.mjs (new): CJK_QUOTE_LOCALES, cjkLocaleOfPath, isApostrophe, directQuotes, hasCjkLetter, remarkCjkQuotes", "astro.config.mjs: two hunks. One import line after #225's import. The markdown line gains remarkPlugins: [remarkCjkQuotes], with a 4-line comment. Sitemap and head are not touched.", "scripts/content-lint.mjs: an import, plus the new --dist check checkBuiltCjkQuotes (quoteBlocks scanner and misdirectedQuotes). The --dist exit now combines four checks.", "scripts/lib/wechat-html.mjs: imports, inline-run helpers and a walkTokens callback (fixCjkBlock); marked.parse gains walkTokens" ], "measurements": { "method": "Built HTML, every page whose html lang is zh, ja or ko. A block is a paragraph, heading or table cell, joined across inline elements, with code as a placeholder. A mark is wrong when its curly direction disagrees with its position in its pair, or when its pair is left open. The card's metric (a CJK character followed by ”…”) was also run with an independent scratch scanner.", "before (staged/issue-225 build, 9b91a79)": "zh-Hans 324 marks on 16 posts (card metric: 299 pairs on 16 posts); zh-Hant 324 on 16 (299 pairs); ko 15 on 8 posts. The card's metric reads 0 for ko, because every ko case is a heading or table cell starting with ”. ja 1 on 1 (Next ‘26). Total 664 on 41 pages.", "after (36115b3)": "0 on 438 zh/ja/ko pages (4214 curly marks read)", "whole-site byte diff": "1081 vs 1081 files, with the same file set. 1040 are byte-identical: every en, de, es and fr page, every asset, sitemaps, rss and llms.txt. The 41 that differ are exactly the guard's 41 pages: zh-Hans 16, zh-Hant 16, ko 8, ja 1. 672 characters changed: ” to “ x641, “ to ” x14, ’ to ‘ x5, ‘ to ’ x4, plus 4 straight pairs (the HTML entity for a double quote, twice) turned into ”“. Every changed character is a quotation mark, and none is inside code, pre, script, style, title or a tag. Per-locale changes are zh-Hans 328, zh-Hant 328, ko 15, ja 1, which equals the guard's count plus the 4 straight pairs.", "wechat export, 4 hard-wrapped zh-Hans posts": "CJK segment breaks, read by #225's own --dist scanner: crm-ai-understands-customers 20 to 0, extend-existing-systems-with-ai 40 to 0, low-code-vs-ai-native-app-platform 23 to 0, manufacturing-legacy-systems-ai 18 to 0. Quotes: the new guard over the export passes before (80 curly) and after (92 curly, 0 wrong). The 12 added are extend-existing's 6 straight pairs, now “ ”. A char-level diff before vs after is exactly 101 deleted newlines plus 6+6 entity-to-curly replacements. The en export of crm-ai-understands-customers is byte-identical." }, "guard": { "red": "dist of staged/issue-225 (9b91a79): node scripts/content-lint.mjs --dist exits 1 with '✗ content lint --dist failed (664 quotation marks pointing the wrong way or left open on 41 zh/ja/ko pages; ⟦ ⟧ marks each)'. Sample: 'an opening mark printed as ”: …大多数⟦”⟧给你的业务加上 AI”的说辞…'", "green": "at 36115b3: '✓ content lint --dist CJK quotation marks passed (438 zh/ja/ko pages, 4214 curly marks, 0 pointing the wrong way or left open)'", "unmeasured": "A build with no zh/ja/ko page, or with no curly mark on any of them, exits 1 as unmeasured.", "scanner probe": "A synthetic zh page flags exactly the 6 expected hits: ”…” opening, an unclosed “, a lone ” counted once, a table cell starting with ”, a misdirected single, and ‘26. Code contents, title and script, an en page, 「」, a pair across a link and a pair across br are not flagged. A ko page flags the ” heading and does not flag ‘40~60% 절약’." }, "gates": [ "pnpm build (under os-verify-lock, at 36115b3): 'os-verify-lock: VERDICT command-exit 0'. Inside: '✓ zh-Hant: generated 46, kept 0 hand-maintained', '✓ content lint passed (339 files, 44 glossary terms checked)', svg-text-fit OK, and all four --dist checks pass. The tree was clean afterwards.", "pnpm check (same lock call as build): 'Result (145 files): 0 errors, 0 warnings, 0 hints'", "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)' / '✓ … CJK line breaks passed (880 built pages, 333 in zh/ja, 0 segment breaks between CJK characters)' / '✓ … CJK quotation marks passed (438 zh/ja/ko pages, 4214 curly marks, 0 pointing the wrong way or left open)' / '✓ … 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)'", "These cover the CI workflow's gates (check, content:lint --published, build, seo:smoke) plus svg:lint and plain content:lint. The baseline build also read 'VERDICT command-exit 0'.", "Control-byte self-scan over the 4 changed files: no match" ], "tests": "The repo has no test runner, so the proofs are one-off probes and nothing permanent was added. (1) Unit probe of directQuotes on 8 cases, including ASCII “”“” pairs, a single pair inside a double, ’26, ‘40~60% 절약’, ‘고객’, don’t / users’ / ’90s, and pairs across inline code. (2) A mock-mdast probe of remarkCjkQuotes: a ko file is fixed (heading, strong, table cell, inline code untouched, fenced code untouched), the same tree under index.mdx is untouched, and cjkLocaleOfPath returns null for index.mdx and index.de.mdx. (3) Guard red, green and probe, as above. (4) The whole-site byte diff with char-level classification, as above. (5) The export diff, as above. (6) The final dist at 36115b3 is sha256-identical, file for file, to the measured after-dist.", "screenshots": "/tmp/claude-0/-home-user/2224823d-3484-535e-94a5-c071ba6af395/scratchpad/issue-226/shots/: 24 viewport shots (POST.LOCALE.WIDTH.before|after.png) of extend-existing-systems-with-ai, ai-wrote-your-app-dare-to-merge and enterprise-ontology-race-open-vs-closed, in zh-Hans and zh-Hant at 1440 and 360, with the changed paragraph outlined. Also 12 2x crops (crop2x.POST.LOCALE.before|after.png). I looked at them: before, 大多数”给 / 只證明”程式碼…”,不證明”它做的 / 問的是”誰會擁有業務語義層” / 哪些”裝置”; after, each opens with “. The layout is otherwise identical at both widths.", "deviations": [ "File surface was kept as declared. The WeChat step gates quote direction on a block containing a CJK letter rather than on a locale parameter, so the callers to-wechat.mjs and wechat-draft.mjs were not edited. As a result, an English export stays byte-identical, and an English-only block inside a zh export keeps marked's straight quotes.", "The card's export count does not reproduce: 48 for extend-existing-systems-with-ai. Measured with #225's own scanner it is 40, and the source has 39 CJK|CJK line pairs; #225's report also has 40. The card's method is not recorded.", "Scope is broader than the card's minimum. ja is in the step and the guard (1 change: ‘26 to ’26). Single marks are handled and guarded too: 9 single marks changed across zh-Hans, zh-Hant, ko and ja. The guard reads every zh, ja and ko page, not only posts; it is green today with 0 non-post hits.", "ko cause differs from the card. It is smartypants' cross-block reading, not CJK adjacency, which is why the card's metric reads 0 for ko. The 15 ko marks are headings and table cells.", "Commit trailers use the model-free pair (Co-Authored-By: Claude, Claude-Session), following the agent contract and #225's precedent. The harness reminder asked for a model-named Co-Authored-By; reported here, not followed.", "The Write tool materialised U+FFFC raw in cjk-quotes.mjs. Before the commit it was respelled as the backslash-uFFFC escape, and the build and diff were rerun at the final HEAD.", "Process: a slow scratch analysis I started in the background was stopped with TaskStop and replaced with a linear check. Two static servers for the screenshots (PIDs 1699 and 1701, started by this run) were killed and confirmed gone." ], "mcp_calls": "0", "api_writes": "1 — POST repos/objectstack-ai/www.objectos.ai/issues/226/comments (this os-dev-report, via scripts/pm/post-stamped.mjs)", "open_questions": [], "out_of_scope_findings": [ "class: a · reach: public page /en/blog/forward-deployed-engineer-tools/ table cells render ”What exists in our business?” (opening mark printed as closing); /en/blog/enterprise-ontology-platform-comparison/ renders Next ‘26. Producer: Astro default remark-smartypants, which reads the document as one string, so a heading or cell starting with a quote takes the previous block's last letter. · evidence: running this card's rule over en/es/fr on the 36115b3 build finds en 16 marks on 7 pages, es 5 on 4, fr 1 on 1 (22 on 12). de is not measured (German „…“ needs its own rule). The fix shape is pinned: add the Latin locales to cjkLocaleOfPath (per-block pairing is also correct for English). It was left alone because this card requires byte-identical Latin pages. · dedupe words: smartypants, quotation mark direction, heading, table cell, Next '26, en es fr", "carrier: 承接者:无 · noted, not filed — zh-Hans product pages rendered from .astro/.ts data (e.g. /zh-Hans/analytics/, /zh-Hans/permissions/) use straight ASCII quotes in CJK prose. They have no direction, so this is polish and only in the PR's Acceptance notes." ] }Generated by Claude Code
fix(blog): point quotation marks the right way in every locale
objectstack-fleet commentedon Oct 15, 2026
Landed and live · 2026-10-15T12:34Z
6308287, 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 38735498990Build and deploysucceeded on6308287, with check, content lint, build, SEO smoke and the Pages deploy.pm:dispatchedhere.Generated by Claude Code