Skip to content

feat(i18n): add localization port contract#106

Merged
flyingrobots merged 27 commits into
mainfrom
cycle/lx-019-localization-port
May 23, 2026
Merged

feat(i18n): add localization port contract#106
flyingrobots merged 27 commits into
mainfrom
cycle/lx-019-localization-port

Conversation

@flyingrobots
Copy link
Copy Markdown
Owner

@flyingrobots flyingrobots commented May 21, 2026

Summary

Adds the LX-019 localization port contract and wires DOGFOOD ordinary text lookup through a runtime-backed LocalizationPort.

This keeps the concrete I18nRuntime at the app composition boundary for catalog loading, shell integration, and locale activation, while views/helpers resolve structured localized objects instead of calling runtime.t() directly.

What changed

  • Added docs/design/LX-019-localization-port-contract.md and updated BEARING/LX legend.
  • Added LocalizationPort, LocalizationRequest, LocalizedObject, status/issue/fact types, and createRuntimeLocalizationPort() in @flyingrobots/bijou-i18n.
  • Added I18nRuntime.localize() so runtime-backed adapters can return immutable structured localization results.
  • Added immutable localized value/result handling for message/resource/data payloads.
  • Moved DOGFOOD text/list helpers behind examples/docs/localization.ts.
  • Updated DOGFOOD i18n workflow docs, @flyingrobots/bijou-i18n README, and changelog.

Non-goals

  • No translation coverage expansion.
  • No dashboard or translation workbench.
  • No CSV/string-table workflow change.
  • No remote localization service.
  • No block localization fetch path.
  • No runtime replacement; this wraps the existing runtime behind a port.

Validation

  • npm test -- --run packages/bijou-i18n/src/runtime.test.ts packages/bijou-i18n/src/localization.test.ts tests/cycles/LX-019/localization-port-dogfood.test.ts tests/cycles/LX-018/runtime-loc-fallbacks.test.ts tests/cycles/LX-008/localized-shell-and-dogfood.test.ts
  • npm run dogfood:i18n:debt
  • npm run dogfood:i18n:check
  • npm run docs:inventory
  • npm run typecheck:test
  • npm run lint
  • git diff --check
  • npm test — 308 files, 3447 tests
  • npm run verify:interactive-examples
  • Pre-push reran typecheck:test, full npm test, and interactive smoke.

Summary by CodeRabbit

  • New Features

    • App-facing localization port delivering structured, immutable localized results; docs and examples updated to use it.
    • Live, surface-backed block previews with explicit nested block rendering for more faithful previews.
    • Toggleable shell performance HUD (backtick) and a new interactive counter demo in docs.
  • Bug Fixes

    • More reliable block preview rendering, snapshotting to prevent mutation, and stricter localization resolution/fallback behavior.
  • Documentation

    • New localization-port design doc, updated i18n workflow, and expanded DOGFOOD locale catalogs.
  • Tests

    • Added coverage for localization port/runtime, block-tree rendering, live previews, and perf HUD.

Review Change Stack

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 21, 2026

Warning

Review limit reached

@flyingrobots, we couldn't start this review because you've used your available PR reviews for now.

Your plan currently allows 1 review/hour. Refill in 49 minutes and 8 seconds.

Your organization has run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After more review capacity refills, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than trial, open-source, and free plans. In all cases, review capacity refills continuously over time.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 1455baaf-7a0f-4fc6-842f-94ec932ef31f

📥 Commits

Reviewing files that changed from the base of the PR and between 1c27e96 and 39e490e.

📒 Files selected for processing (16)
  • docs/CHANGELOG.md
  • examples/docs/app.ts
  • packages/bijou-i18n/README.md
  • packages/bijou-i18n/src/index.ts
  • packages/bijou-i18n/src/localization.test.ts
  • packages/bijou-i18n/src/localization.ts
  • packages/bijou-i18n/src/runtime.test.ts
  • packages/bijou-i18n/src/runtime.ts
  • packages/bijou-tui/src/app-frame-palette.ts
  • packages/bijou-tui/src/app-frame-render.test.ts
  • packages/bijou-tui/src/app-frame-render.ts
  • packages/bijou-tui/src/app-frame-types.ts
  • packages/bijou-tui/src/app-frame-utils.ts
  • packages/bijou-tui/src/app-frame.test.ts
  • packages/bijou-tui/src/app-frame.ts
  • tests/cycles/LX-011/dogfood-locale-ratchet.test.ts
📝 Walkthrough

Walkthrough

Adds a LocalizationPort contract and runtime adapter, threads port-based localization through the docs app and DOGFOOD previews, implements block-tree rendering with Surface-backed nested previews, and introduces a backtick-toggled Perf HUD; includes tests, catalogs, and docs/changelog updates.

Changes

LocalizationPort Implementation & Integration

Layer / File(s) Summary
Localization contract and types
packages/bijou-i18n/src/localization.ts, docs/design/LX-019-localization-port-contract.md
Adds LocalizationPort, LocalizedObject, request/issue/fact types and exports.
Port factory & immutability helpers
packages/bijou-i18n/src/localization.ts, packages/bijou-i18n/src/index.ts, packages/bijou-i18n/README.md
Implements createRuntimeLocalizationPort(), freezeLocalizedObject(), freezeLocalizedValue() and re-exports/documentation.
Runtime localize() & resolution logic
packages/bijou-i18n/src/runtime.ts
Adds I18nRuntime.localize() with structured resolution (translated/fallback/missing) and issues/facts; t() delegates to new flow.
Localization runtime & port tests
packages/bijou-i18n/src/localization.test.ts, packages/bijou-i18n/src/runtime.test.ts
Vitest suites covering translated/fallback/missing statuses, frozen results, resource handling, and formatList delegation.
App-level localization helpers
examples/docs/localization.ts, examples/docs/locale.ts
Adds interpolateFallbackText, localizedText, dogfoodLocalizedText, and formatLocalizedList; locale helpers now accept LocalizationPort.
Docs app wiring & usage
examples/docs/app.ts, examples/docs/*
Creates runtime LocalizationPort via createRuntimeLocalizationPort(i18n) and threads it into landing/explorer panes, footers, titles, and DOGFOOD preview flows; adds scroll-reset sync.
DOGFOOD localization test & catalogs
tests/cycles/LX-019/localization-port-dogfood.test.ts, examples/docs/i18n/catalogs/*/bijou.dogfood.json
Adds mock-port test and new blocks.preview.* message keys across locales; updates i18n workflow docs to use the port.
Changelog / BEARING / DOGFOOD docs
docs/CHANGELOG.md, docs/BEARING.md, docs/DOGFOOD.md, docs/method/legends/*
Documents LocalizationPort, surface-backed previews, and marks LX-019 active.

Block-Tree Rendering & Standard Blocks

Layer / File(s) Summary
Block-tree contract & implementation
packages/bijou/src/core/block-tree-render.ts, packages/bijou/src/index.ts
Adds blockRenderNode, isBlockRenderNode, renderBlockTree, snapshotting, slot resolution recursion, facts aggregation, depth limit and freezing.
Standard blocks: visual Surface output
packages/bijou/src/core/standard-blocks.ts
Standard blocks can return Surface visual outputs; adds renderVisualSectionsSurface, visual-slot handling, surface stacking/boxing and fit/clipping helpers.
Block-tree & standard-block tests
packages/bijou/src/core/block-tree-render.test.ts, packages/bijou/src/core/standard-blocks.test.ts
New and extended tests validating nested rendering, mode inheritance, snapshotting, accessor avoidance, surface preservation and clipping.
DOGFOOD blocks preview integration
examples/docs/app.ts, tests/cycles/DX-031/dogfood-blocks-section.test.ts
Blocks preview composes live Surface examples via renderBlockTree; tests assert surface-backed previews and preserve styling metadata.

Perf HUD / App Frame

Layer / File(s) Summary
Perf HUD overlay & actions
packages/bijou-tui/src/app-frame.ts, packages/bijou-tui/src/app-frame-actions.ts, packages/bijou-tui/src/app-frame-types.ts
Adds perfHudOpen model flag, toggle-perf-hud action, renderFramePerfHudOverlay overlay, and wiring into key-routing and initial state.
Keybinding, i18n and tests
packages/bijou-tui/src/app-frame-utils.ts, packages/bijou-tui/src/app-frame-i18n.ts, packages/bijou-tui/src/app-frame.test.ts, tests/cycles/DF-001/dogfood-coverage-progress.test.ts
Binds backtick key to toggle HUD, adds perfHud i18n entries, tests toggle behavior from workspace/help layers and landing docs integration test.
Docs/README updates
packages/bijou-tui/GUIDE.md, examples/docs/README.md
Documents backtick HUD shortcut and README/GUIDE bullet updates.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

🐰 I hopped through ports and trees,
Froze the facts beneath the breeze,
Surfaces live, previews play,
Backtick HUD to light the way,
A rabbit cheers this tidy day!

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch cycle/lx-019-localization-port

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f7dd7c307d

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/bijou-i18n/src/runtime.ts Outdated
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (1)
packages/bijou-i18n/src/localization.ts (1)

117-140: 💤 Low value

freezeLocalizedValue has a few quiet edge cases for arbitrary data payloads.

Since LocalizedObject<Value = unknown> is used for data/resource payloads, the deep-freeze helper may want to be a bit more defensive:

  • Plain-object reconstruction via Object.entries(Object.getOwnPropertyDescriptors(value)) silently drops symbol-keyed properties and demotes non-enumerable own properties to enumerable. For JSON-derived catalogs this is fine, but programmatically built data payloads will lose properties without any warning.
  • There is no cycle guard. A self-referencing data payload would recurse until the call stack blows.

If those shapes are intentionally out-of-scope, consider documenting the invariant in the JSDoc (e.g., "values must be JSON-shaped, no symbols, no cycles, no accessors") alongside the existing accessor-property throw at line 134, so adapters know what they are required to produce.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/bijou-i18n/src/localization.ts` around lines 117 - 140,
freezeLocalizedValue currently loses symbol-keyed and non-enumerable own
properties and has no cycle protection; update freezeLocalizedValue to (1)
accept a traversal context (e.g., a WeakSet seen) and throw on cycles or return
already-frozen object to avoid infinite recursion, (2) iterate own keys using
Object.getOwnPropertyNames + Object.getOwnPropertySymbols and copy property
descriptors instead of using Object.entries so non-enumerable flags are
preserved, applying freezeLocalizedValue recursively to descriptor.value while
still throwing for accessor descriptors (preserve the existing accessor error),
and (3) after defining all properties on a new plain object via
Object.defineProperty, freeze and return it; alternatively, if you prefer to
keep the simpler behavior, add a JSDoc on freezeLocalizedValue / LocalizedObject
stating the JSON-shaped invariant (no symbols, no non-enumerable props, no
cycles, no accessors) so callers know the requirements.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@examples/docs/app.ts`:
- Around line 3398-3403: The pages array built in createDocsExplorerApp
snapshots localized strings (calls like pageTitle(spec.id, localization) and
searchTitle(...)) at construction so labels never update after cycle-locale;
change pages metadata to compute titles lazily or recompute on locale change —
e.g., replace stored title/searchTitle with functions/getters that call
pageTitle(spec.id, currentLocalization) at render-time or add a locale change
listener in createDocsExplorerApp that maps DOCS_SITE_PAGES (including the
COMPONENTS_PAGE_ID branch and entries using componentsPageKeys) and updates the
pages array by re-running pageTitle/searchTitle using the new localization;
ensure any code that reads page.title or page.searchTitle invokes the resolver
or reads the refreshed value.

In `@examples/docs/content/guides-i18n-workflow.md`:
- Around line 99-109: The example assigns the LocalizedObject returned by
LocalizationPort.resolve<Value>() to label instead of using its .value; update
the snippet so the call to localization.resolve (from
createRuntimeLocalizationPort/runtime) returns a LocalizedObject (e.g.,
labelResult) and then read the optional localized string from labelResult.value
(handling undefined if needed) so consumers get the actual string rather than
the wrapper object.

In `@examples/docs/localization.ts`:
- Around line 44-48: formatLocalizedList currently calls localization.formatList
directly which can throw and bypass the fallback; wrap the call in a try/catch
like localizedText does: if localization is provided, call
localization.formatList(values) inside a try block, return the result on
success, and on any thrown error (or if localization is undefined) return
values.join(', '); reference the formatLocalizedList function and the
localization.formatList call to implement this safe fallback.

In `@packages/bijou-i18n/src/runtime.ts`:
- Around line 395-397: The bug: localize forwards options.missingMessage into
resolveLocalizedValueResult unconditionally, which causes entries with
entry.kind === 'resource'|'data' to end up with a message string as their value
(wrong kind). Fix: in localize skip passing options.missingMessage when
entry.kind is 'resource' or 'data' (mirror resource() behavior) so call
resolveLocalizedValueResult without missingMessage for those kinds; update the
invocation in localize and ensure resolveLocalizedValueResult is still used for
other kinds unchanged.

---

Nitpick comments:
In `@packages/bijou-i18n/src/localization.ts`:
- Around line 117-140: freezeLocalizedValue currently loses symbol-keyed and
non-enumerable own properties and has no cycle protection; update
freezeLocalizedValue to (1) accept a traversal context (e.g., a WeakSet seen)
and throw on cycles or return already-frozen object to avoid infinite recursion,
(2) iterate own keys using Object.getOwnPropertyNames +
Object.getOwnPropertySymbols and copy property descriptors instead of using
Object.entries so non-enumerable flags are preserved, applying
freezeLocalizedValue recursively to descriptor.value while still throwing for
accessor descriptors (preserve the existing accessor error), and (3) after
defining all properties on a new plain object via Object.defineProperty, freeze
and return it; alternatively, if you prefer to keep the simpler behavior, add a
JSDoc on freezeLocalizedValue / LocalizedObject stating the JSON-shaped
invariant (no symbols, no non-enumerable props, no cycles, no accessors) so
callers know the requirements.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: f08ae5b0-895c-4707-8ec3-0d934db12f16

📥 Commits

Reviewing files that changed from the base of the PR and between d992dc6 and d91fc32.

⛔ Files ignored due to path filters (1)
  • examples/docs/i18n/source/dogfood-strings.csv is excluded by !**/*.csv
📒 Files selected for processing (20)
  • docs/BEARING.md
  • docs/CHANGELOG.md
  • docs/DOGFOOD.md
  • docs/design/LX-019-localization-port-contract.md
  • docs/method/legends/LX-localization-and-bidirectionality.md
  • examples/docs/app.ts
  • examples/docs/content/guides-i18n-workflow.md
  • examples/docs/i18n/catalogs/de/bijou.dogfood.json
  • examples/docs/i18n/catalogs/en/bijou.dogfood.json
  • examples/docs/i18n/catalogs/es/bijou.dogfood.json
  • examples/docs/i18n/catalogs/fr/bijou.dogfood.json
  • examples/docs/locale.ts
  • examples/docs/localization.ts
  • packages/bijou-i18n/README.md
  • packages/bijou-i18n/src/index.ts
  • packages/bijou-i18n/src/localization.test.ts
  • packages/bijou-i18n/src/localization.ts
  • packages/bijou-i18n/src/runtime.ts
  • tests/cycles/DX-031/dogfood-blocks-section.test.ts
  • tests/cycles/LX-019/localization-port-dogfood.test.ts

Comment thread examples/docs/app.ts
Comment thread examples/docs/content/guides-i18n-workflow.md
Comment thread examples/docs/localization.ts Outdated
Comment thread packages/bijou-i18n/src/runtime.ts Outdated
@flyingrobots
Copy link
Copy Markdown
Owner Author

@codex Self-review findings from the Code Lawyer pass. I found two behavior-level issues that should be resolved before merge.

Severity File / Lines Infraction Evidence Recommended mitigation prompt
P1 High packages/bijou-i18n/src/runtime.ts:435-440 Public runtime method t() depends on this, so destructuring the method crashes at runtime. const { t } = runtime; t(key) throws Cannot read properties of undefined (reading 'localize') because t() calls this.localize(...). Add a deterministic regression test proving destructured runtime.t works, then refactor t() to call a closure-local localize() helper or otherwise avoid this in returned runtime object methods.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 96ba79ccab

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/bijou-i18n/src/runtime.ts Outdated
Comment thread packages/bijou-i18n/src/runtime.ts Outdated
@flyingrobots
Copy link
Copy Markdown
Owner Author

@codex Self-review findings from local diff against origin/main.

Severity File Lines Issue Recommended mitigation
P1 packages/bijou-i18n/src/runtime.ts 435-440 I18nRuntime.t() now calls this.localize(), so detached method use crashes with Cannot read properties of undefined (reading 'localize'). Port-style runtime APIs should not require a JavaScript receiver binding to remain usable. Extract the localization implementation into a closure-local helper, for example localizeRequest(), and have both runtime.localize() and runtime.t() call that helper. Add a regression in packages/bijou-i18n/src/runtime.test.ts that destructures const { t } = runtime and verifies message resolution still works.
P2 packages/bijou/src/core/standard-blocks.ts 575-586, 677-684, 720-731 Visual AppShell composition still flattens nested child Surface output into plain text via surfaceSlotText() and then wraps it in createTextSurface(). That drops child cell styling/modifiers/colors and keeps the implementation closer to “printing a rendered block” than compositing nested surfaces. Let visual render sections carry Surface content for interactive/static modes, then box/blit the actual child surface instead of converting it to plain text. Keep pipe/access lowering text explicit. Add a regression that a styled cell from a nested child block survives after renderBlockTree() renders it through AppShell.
P3 packages/bijou/src/core/block-tree-render.ts; packages/bijou/src/core/block-tree-render.test.ts 46-49; 108-114 blockRenderNode() shallow-freezes input, so caller-owned nested slot objects can mutate after node creation and change future render output. The current max-depth test relies on this mutable post-construction slot injection, which undercuts the runtime-truth posture of branded render nodes. Snapshot/freeze own data props for plain records and arrays in blockRenderNode() while preserving non-plain renderable values such as Surface and block definitions. Add a regression showing mutation of the original slots object after construction does not affect later rendering, and rewrite the depth test to build an explicit nested chain.

No files were changed during this review pass.

@chatgpt-codex-connector
Copy link
Copy Markdown

Summary

Testing

  • npm test -- --run packages/bijou-i18n/src/runtime.test.ts packages/bijou/src/core/block-tree-render.test.ts packages/bijou/src/core/standard-blocks.test.ts
  • ⚠️ npm run lint (workspace-level pre-existing type-resolution/build-order failures unrelated to this change set in multiple packages)

Also completed:

View task →

@flyingrobots
Copy link
Copy Markdown
Owner Author

@codex Addressed the self-review findings in 4ce8a998.

Severity Issue Files Regression coverage Outcome
P1 I18nRuntime.t() required a method receiver because it called this.localize() packages/bijou-i18n/src/runtime.ts, packages/bijou-i18n/src/runtime.test.ts keeps message lookup independent from method receiver binding; npm test -- --run packages/bijou-i18n/src/runtime.test.ts t() now calls a closure-local localization helper, so destructured/passed helpers keep runtime state.
P2 Nested visual block rendering flattened child Surface output to text and lost cell styling packages/bijou/src/core/standard-blocks.ts, packages/bijou/src/core/standard-blocks.test.ts, docs/design/DX-031F-block-tree-rendering.md preserves child surface cells when visual blocks render nested block output; npm test -- --run packages/bijou/src/core/standard-blocks.test.ts Visual sections now carry Surface content directly and box/blit child surfaces without recreating cells from text.
P3 blockRenderNode() shallow-froze input, so caller-owned slot/config records could mutate later render output packages/bijou/src/core/block-tree-render.ts, packages/bijou/src/core/block-tree-render.test.ts, docs/design/DX-031F-block-tree-rendering.md snapshots slot records when creating render nodes; npm test -- --run packages/bijou/src/core/block-tree-render.test.ts Render nodes now snapshot plain records/arrays while preserving branded block values; the depth test no longer relies on post-construction mutation.

Validation after the fix:

  • npm test -- --run packages/bijou-i18n/src/runtime.test.ts packages/bijou/src/core/block-tree-render.test.ts packages/bijou/src/core/standard-blocks.test.ts
  • npm run docs:inventory
  • git diff --check
  • npm run typecheck:test
  • npm run lint
  • npm test
  • npm run verify:interactive-examples
  • pre-commit reran npm run lint and lockfile consistency
  • pre-push reran npm run typecheck:test, npm test, and npm run verify:interactive-examples

@chatgpt-codex-connector
Copy link
Copy Markdown

Summary

  • I checked the repository state and confirmed there are no uncommitted changes to apply for this trigger right now; the branch is clean and already contains commit 4ce8a99 (“Fix: harden localization and nested block rendering”). AGENTS.mdL1-L52
  • Because there were no new code changes to commit, I did not create another commit and did not call make_pr (to stay consistent with your workflow constraints). AGENTS.mdL5-L9

Testing

  • cd /workspace/bijou && rg --files -g 'AGENTS.md' && git status --short && git log --oneline -5

View task →

@flyingrobots
Copy link
Copy Markdown
Owner Author

@codex Follow-up pushed in 0ebfa52.\n\n| Issue | Resolution | Regression |\n|---|---|---|\n| Blocks preview looked like raw printed/debug text | Selected block pages now show concise documentation, hide embedded lowering scrollbars, and keep raw metadata summaries/source paths out of the TUI surface. | tests/cycles/DX-031/dogfood-blocks-section.test.ts |\n| Backtick no longer opened the perf overlay | createFramedApp now owns a shell-level perf HUD on backtick, including active help/shell layers; DOGFOOD landing routes that key into the docs shell. | packages/bijou-tui/src/app-frame.test.ts; tests/cycles/DF-001/dogfood-coverage-progress.test.ts |\n\nValidation run before push: focused regressions, docs:inventory, dogfood:i18n:debt, typecheck:test, lint, git diff --check, npm test, verify:interactive-examples. Pre-push reran typecheck:test, full npm test, and interactive example smoke.

@chatgpt-codex-connector
Copy link
Copy Markdown

No follow-up changes were required from this trigger: the branch is already clean, and the referenced follow-up commit (0ebfa52) is present at HEAD in the recent history.

Given there were no new modifications to commit, I did not create a commit and did not call make_pr (per your workflow rule that make_pr must only be called when a new commit is made). AGENTS.mdL44-L50

Checks run

  • cd /workspace/bijou && rg --files -g 'AGENTS.md' && git status --short && git log --oneline -5

View task →

@flyingrobots
Copy link
Copy Markdown
Owner Author

@codex update pushed in 3b6ad96.\n\n| Area | Outcome | Coverage |\n|---|---|---|\n| Blocks side nav | Standard block rows now select their preview when focused, so the nav row, guide info pane, and main live preview describe the same block. | tests/cycles/DX-031/dogfood-blocks-section.test.ts |\n| Blocks live preview | Lower-mode output is now summarized as mode/fact data instead of rendering clipped nested mini-surfaces into the preview page. | tests/cycles/DX-031/dogfood-blocks-section.test.ts |\n| Nested visual blocks | Standard block visual sections fit oversized child surfaces to the parent section, preventing nested blocks from overrunning AppShell preview regions. | packages/bijou/src/core/standard-blocks.test.ts |\n\nValidation run before push:\n- npm test -- --run packages/bijou-tui/src/app-frame.test.ts tests/cycles/DF-001/dogfood-coverage-progress.test.ts packages/bijou/src/core/standard-blocks.test.ts tests/cycles/DX-031/dogfood-blocks-section.test.ts\n- npm run docs:inventory\n- npm run dogfood:i18n:debt\n- npm run typecheck:test\n- npm run lint\n- git diff --check\n- npm test\n- npm run verify:interactive-examples\n\nPre-push also reran typecheck:test, the full test suite, and scripted interactive example smoke.

@chatgpt-codex-connector
Copy link
Copy Markdown

Summary

  • No follow-up code changes were needed for this trigger; the working tree is clean and 3b6ad96 is already at HEAD, so there was nothing new to commit.
  • I therefore did not run git commit and did not call make_pr, to stay consistent with the workflow rule to only create PR metadata when new committed changes exist. AGENTS.mdL44-L53

Testing

  • cd /workspace/bijou && rg --files -g 'AGENTS.md' && git status --short && git log --oneline -5

View task →

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
tests/cycles/DX-031/dogfood-blocks-section.test.ts (1)

54-66: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Check every match on the row, not just the first one.

row.indexOf(needle) only inspects the first occurrence. If an unstyled copy appears before the styled one, this helper returns false even though the frame does contain a styled match.

Suggested fix
-    const start = row.indexOf(needle);
-    if (start === -1) continue;
-
-    for (let x = start; x < Math.min(frame.width, start + needle.length); x++) {
-      const cell = frame.get(x, y);
-      if (
-        cell.fg != null
-        || cell.fgRGB != null
-        || (cell.modifiers?.length ?? 0) > 0
-      ) {
-        return true;
-      }
-    }
+    for (let start = row.indexOf(needle); start !== -1; start = row.indexOf(needle, start + 1)) {
+      for (let x = start; x < Math.min(frame.width, start + needle.length); x++) {
+        const cell = frame.get(x, y);
+        if (
+          cell.fg != null
+          || cell.fgRGB != null
+          || (cell.modifiers?.length ?? 0) > 0
+        ) {
+          return true;
+        }
+      }
+    }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/cycles/DX-031/dogfood-blocks-section.test.ts` around lines 54 - 66, The
current logic only checks the first occurrence of needle on a row via
row.indexOf(needle) and can miss later styled matches; change it to iterate over
every occurrence by repeatedly calling row.indexOf(needle, fromIndex) (or a
while loop that updates fromIndex = start + 1) and for each found start run the
existing inner loop that checks frame.get(x, y) (preserving the
Math.min(frame.width, start + needle.length) bounds); if any occurrence has
styled cells return true, otherwise continue scanning further occurrences and
rows.
♻️ Duplicate comments (2)
packages/bijou-i18n/src/runtime.ts (1)

365-367: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Only pass missingMessage for message entries.

localize() still forwards options.missingMessage for every entry kind. When a resource or data entry is missing in the selected locale, this can return a marker string while kind stays non-message, which breaks the LocalizedObject<Value> contract. resource() already avoids this; localize() should mirror it here.

Suggested fix
     const resolved = resolveLocalizedValueResult<Value>(entry as I18nCatalogEntry<Value>, new Set<string>(), {
-      missingMessage: options.missingMessage,
+      missingMessage: entry.kind === 'message' ? options.missingMessage : undefined,
     });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/bijou-i18n/src/runtime.ts` around lines 365 - 367, The code
currently always passes options.missingMessage into resolveLocalizedValueResult
which causes non-message entries (resource/data) to get marker strings while
retaining a non-message kind; update localize() so it only forwards
options.missingMessage when the entry is a message entry (check entry.kind ===
'message' or similar on the I18nCatalogEntry<Value>), mirroring the resource()
behavior, so resolveLocalizedValueResult is called with missingMessage only for
message entries and leaves resource/data entries untouched to preserve the
LocalizedObject<Value> contract.
examples/docs/app.ts (1)

3441-3446: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Don't snapshot localized page metadata at app construction.

These title / searchTitle strings are still resolved once while building pages. After cycle-locale, the runtime locale changes, but the header tabs and palette titles keep the old language until the app is recreated.

Also applies to: 3514-3514, 3533-3533, 3589-3590

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@examples/docs/app.ts` around lines 3441 - 3446, The pages array is currently
capturing localized strings at construction (e.g., title: pageTitle(spec.id,
localization)), so titles/searchTitles don't update after locale changes; change
those entries (for the case when spec.id === COMPONENTS_PAGE_ID and the other
noted spots) to defer resolution by storing either a function/closure or a key
instead of the resolved string—for example replace title/searchTitle with a
thunk (e.g., title: () => pageTitle(spec.id, localization)) or keep the
locale-independent id and call pageTitle(spec.id, currentLocalization) at
render/palette time; update handling code that reads pages to call the thunk or
resolve the key to get current localized text (apply same change where
DOCS_SITE_PAGES is mapped at the other referenced locations).
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/bijou-i18n/src/runtime.ts`:
- Around line 384-386: The status calculation uses resolved and issues but the
code drops the LocalizedResolution when encountering a $ref; update the
reference resolution path (where $ref entries are resolved) to propagate the
full LocalizedResolution object instead of replacing it with a raw value so that
resolved.status and resolved.issues reflect the referenced entry. Specifically,
inside the reference-resolution logic (the code that produces resolved for $ref
entries), return/assign the referenced LocalizedResolution (including its status
and issues) and merge or prefer its issues/status before the status calc that
uses issues.some(...) and resolved.status in localize()/LocalizedResolution
creation.

---

Outside diff comments:
In `@tests/cycles/DX-031/dogfood-blocks-section.test.ts`:
- Around line 54-66: The current logic only checks the first occurrence of
needle on a row via row.indexOf(needle) and can miss later styled matches;
change it to iterate over every occurrence by repeatedly calling
row.indexOf(needle, fromIndex) (or a while loop that updates fromIndex = start +
1) and for each found start run the existing inner loop that checks frame.get(x,
y) (preserving the Math.min(frame.width, start + needle.length) bounds); if any
occurrence has styled cells return true, otherwise continue scanning further
occurrences and rows.

---

Duplicate comments:
In `@examples/docs/app.ts`:
- Around line 3441-3446: The pages array is currently capturing localized
strings at construction (e.g., title: pageTitle(spec.id, localization)), so
titles/searchTitles don't update after locale changes; change those entries (for
the case when spec.id === COMPONENTS_PAGE_ID and the other noted spots) to defer
resolution by storing either a function/closure or a key instead of the resolved
string—for example replace title/searchTitle with a thunk (e.g., title: () =>
pageTitle(spec.id, localization)) or keep the locale-independent id and call
pageTitle(spec.id, currentLocalization) at render/palette time; update handling
code that reads pages to call the thunk or resolve the key to get current
localized text (apply same change where DOCS_SITE_PAGES is mapped at the other
referenced locations).

In `@packages/bijou-i18n/src/runtime.ts`:
- Around line 365-367: The code currently always passes options.missingMessage
into resolveLocalizedValueResult which causes non-message entries
(resource/data) to get marker strings while retaining a non-message kind; update
localize() so it only forwards options.missingMessage when the entry is a
message entry (check entry.kind === 'message' or similar on the
I18nCatalogEntry<Value>), mirroring the resource() behavior, so
resolveLocalizedValueResult is called with missingMessage only for message
entries and leaves resource/data entries untouched to preserve the
LocalizedObject<Value> contract.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 68855073-fccc-4470-b289-cf57dd5b2340

📥 Commits

Reviewing files that changed from the base of the PR and between d91fc32 and 3b6ad96.

⛔ Files ignored due to path filters (1)
  • examples/docs/i18n/source/dogfood-strings.csv is excluded by !**/*.csv
📒 Files selected for processing (31)
  • docs/CHANGELOG.md
  • docs/design-system/blocks.md
  • docs/design/DX-031-standard-bijou-blocks.md
  • docs/design/DX-031F-block-tree-rendering.md
  • docs/design/LX-019-localization-port-contract.md
  • examples/docs/README.md
  • examples/docs/app.ts
  • examples/docs/i18n/catalogs/de/bijou.dogfood.json
  • examples/docs/i18n/catalogs/en/bijou.dogfood.json
  • examples/docs/i18n/catalogs/es/bijou.dogfood.json
  • examples/docs/i18n/catalogs/fr/bijou.dogfood.json
  • packages/bijou-i18n/src/runtime.test.ts
  • packages/bijou-i18n/src/runtime.ts
  • packages/bijou-tui/GUIDE.md
  • packages/bijou-tui/README.md
  • packages/bijou-tui/src/app-frame-actions.ts
  • packages/bijou-tui/src/app-frame-i18n.ts
  • packages/bijou-tui/src/app-frame-types.ts
  • packages/bijou-tui/src/app-frame-utils.ts
  • packages/bijou-tui/src/app-frame.test.ts
  • packages/bijou-tui/src/app-frame.ts
  • packages/bijou-tui/src/layout-preset.test.ts
  • packages/bijou/GUIDE.md
  • packages/bijou/README.md
  • packages/bijou/src/core/block-tree-render.test.ts
  • packages/bijou/src/core/block-tree-render.ts
  • packages/bijou/src/core/standard-blocks.test.ts
  • packages/bijou/src/core/standard-blocks.ts
  • packages/bijou/src/index.ts
  • tests/cycles/DF-001/dogfood-coverage-progress.test.ts
  • tests/cycles/DX-031/dogfood-blocks-section.test.ts
✅ Files skipped from review due to trivial changes (12)
  • packages/bijou-tui/GUIDE.md
  • docs/design/DX-031F-block-tree-rendering.md
  • docs/design-system/blocks.md
  • examples/docs/README.md
  • packages/bijou-tui/README.md
  • docs/design/DX-031-standard-bijou-blocks.md
  • examples/docs/i18n/catalogs/fr/bijou.dogfood.json
  • packages/bijou-tui/src/layout-preset.test.ts
  • packages/bijou/README.md
  • docs/design/LX-019-localization-port-contract.md
  • examples/docs/i18n/catalogs/de/bijou.dogfood.json
  • docs/CHANGELOG.md

Comment thread packages/bijou-i18n/src/runtime.ts
@flyingrobots
Copy link
Copy Markdown
Owner Author

@codex follow-up pushed in ef285bb. The remaining screenshot was the parent Block Preview row still behaving as a prose inventory page. That row is now treated as a preview-group entry: selecting/opening it lands on the first concrete standard block preview, so the center pane starts on a live rendered block surface instead of the old inventory text.\n\nAdditional coverage:\n- tests/cycles/DX-031/dogfood-blocks-section.test.ts now proves blocks-preview resolves to the first standard block preview.\n- DX-031F design doc and CHANGELOG now record the preview-group behavior.\n\nValidation after this follow-up:\n- npm test -- --run tests/cycles/DX-031/dogfood-blocks-section.test.ts packages/bijou/src/core/standard-blocks.test.ts\n- npm test -- --run packages/bijou-tui/src/app-frame.test.ts tests/cycles/DF-001/dogfood-coverage-progress.test.ts packages/bijou/src/core/standard-blocks.test.ts tests/cycles/DX-031/dogfood-blocks-section.test.ts\n- npm run docs:inventory\n- npm run dogfood:i18n:debt\n- npm run typecheck:test\n- npm run lint\n- git diff --check\n- npm test\n- npm run verify:interactive-examples\n\nPre-push also reran typecheck:test, the full test suite, and scripted interactive examples.

@chatgpt-codex-connector
Copy link
Copy Markdown

Summary

Testing

  • npm test -- --run tests/cycles/DX-031/dogfood-blocks-section.test.ts (fails in this environment due to package entry resolution for @flyingrobots/bijou-tui before tests execute).

View task →

@chatgpt-codex-connector
Copy link
Copy Markdown

Summary

Committed: 8c6484d
PR metadata recorded via make_pr.

Testing

  • npm test -- --run packages/bijou-i18n/src/localization.test.ts packages/bijou/src/core/block-tree-render.test.ts tests/cycles/LX-019/localization-port-dogfood.test.ts
  • ⚠️ npm run lint (workspace-wide pre-existing type-resolution/build-order failures unrelated to this patch; command output shows missing module/type artifacts across multiple packages).

View task →

@flyingrobots
Copy link
Copy Markdown
Owner Author

Addressed the self-review findings in 31f68067 (Fix: harden localization and block preview review issues).

Severity Issue Commit Regression coverage Outcome
P1 freezeLocalizedValue() did not enforce the documented JSON-shaped localized-value boundary 31f68067 packages/bijou-i18n/src/localization.test.ts; focused suite + full npm test Added deterministic rejection for cycles, symbol keys, non-enumerables, accessors, class instances, functions, symbols, and bigint values.
P2 blockRenderNode() could recurse until stack overflow on cyclic slot/config records 31f68067 packages/bijou/src/core/block-tree-render.test.ts; focused suite + full npm test Added cycle detection and deterministic contract errors during render-node input snapshotting.
P3 DOGFOOD localized text helper swallowed all localization-port failures 31f68067 tests/cycles/LX-019/localization-port-dogfood.test.ts; focused suite + full npm test Unexpected port failures now remain observable; fallback still applies for absent/non-string localized values.
P4 Counter fixture width used raw string length on styled terminal output 31f68067 tests/cycles/DX-031/dogfood-blocks-section.test.ts; focused suite + full npm test Counter preview now uses ANSI-aware graphemeWidth() for width measurement.
P5 LX-019 design doc used an inconsistent legend link 31f68067 npm run docs:inventory; git diff --check Link now matches existing docs/design/LX-*.md convention.

Validation run locally before push and again via pre-push where applicable:

  • npm test -- --run packages/bijou-i18n/src/localization.test.ts packages/bijou/src/core/block-tree-render.test.ts tests/cycles/LX-019/localization-port-dogfood.test.ts tests/cycles/DX-031/dogfood-blocks-section.test.ts
  • git diff --check
  • npm run docs:inventory
  • npm run typecheck:test
  • npm run lint
  • npm test — 309 files, 3476 tests passed
  • pre-push: npm run typecheck:test, npm test, npm run verify:interactive-examples

✅ Addressed in commits: 31f68067

@flyingrobots
Copy link
Copy Markdown
Owner Author

@codex Self-review findings for PR #106. Please confirm or challenge these before we merge.

Severity File Lines Type Evidence Recommended mitigation prompt
P1 packages/bijou-i18n/src/runtime.ts 467-472 Runtime immutability leak The PR adds localize() and hardens LocalizedObject.value through freezeLocalizedObject(), but I18nRuntime.resource() still returns resolveLocalizedValue(...) directly. Object/array resource values can escape by reference, be mutated by callers, and silently mutate catalog-backed runtime state. That violates the new port contract's immutable localized-object boundary. Add a regression test proving a resource object/array returned from runtime.resource() is frozen and cannot mutate later lookups. Then implement resource() through the same localized-value freeze path used by localize({ kind: 'resource' }), preserving existing missing-reference behavior. Update CHANGELOG because this tightens a public API invariant.
P2 packages/bijou-i18n/src/runtime.ts 443-448 Atomicity / state consistency setLocale() assigns currentLocale and currentDirection before awaiting activateLoaderLocale(locale). If an async catalog loader rejects, the runtime remains on the failed locale/direction while still holding the previous loader catalog state. That creates a split-brain localization runtime. Add a deterministic regression with a loader that rejects for the next locale; assert runtime.locale, runtime.direction, and message resolution remain on the previous locale after rejection. Fix by loading/preparing catalogs first and committing locale/direction/catalog state only after the load succeeds.
P3 examples/docs/app.ts 816-824, 826-834, 3985-3987 Adapter override loss DOGFOOD loads extraI18nCatalogs once during app construction, but every language cycle calls loadDogfoodRuntimeCatalogs(), which unloads the whole bijou.dogfood namespace and reloads only generated locale catalogs. Any host/test-provided DOGFOOD catalog override is lost after the first locale cycle. Add a regression that starts DOGFOOD with an extraI18nCatalogs override for a visible DOGFOOD key, cycles language, and proves the override still participates. Fix by re-applying extra catalogs during applyDogfoodLocale() or by separating generated DOGFOOD catalogs from caller-provided override catalogs so namespace reloads do not discard adapters.

No merge recommended until these are resolved or explicitly ruled out with evidence.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

return resolveLocalizedValue(entry as I18nCatalogEntry<T>, new Set<string>());

P1 Badge Return frozen clones from resource()

The new localization contract in this change introduces deep-frozen localized values via localize(), but resource() still returns the catalog-backed value by reference. When a caller reads an object/array resource and mutates it, that mutation leaks back into runtime state and changes later lookups for the same key, which breaks the immutability boundary this PR establishes for localized payloads and can cause hard-to-debug cross-request state corruption.

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@flyingrobots
Copy link
Copy Markdown
Owner Author

@codex Follow-up from the self-review findings is pushed.

Severity File Issue Outcome Commit SHA Regression Test
P1 packages/bijou-i18n/src/runtime.ts I18nRuntime.resource() returned mutable catalog-backed resource objects. resource() now returns cloned, deeply frozen values through the same portable localized value boundary used by localize(). 8ae4814d returns frozen resource values without leaking catalog state; npm test -- --run packages/bijou-i18n/src/runtime.test.ts
P2 packages/bijou-i18n/src/runtime.ts Loader-backed setLocale() could leave locale/direction mutated after a loader failure. setLocale() now loads the next locale catalogs before committing locale, direction, or loader catalog state. e83ee7b5 keeps the active locale stable when loader-backed locale switching fails; npm test -- --run packages/bijou-i18n/src/runtime.test.ts
P3 examples/docs/app.ts DOGFOOD language cycling discarded caller-provided extraI18nCatalogs after unloading the DOGFOOD namespace. DOGFOOD now reloads extra catalogs after generated selected-locale catalogs during initial load and locale changes. 1c27e962 preserves extra DOGFOOD catalog overrides after language cycling; npm test -- --run tests/cycles/LX-011/dogfood-locale-ratchet.test.ts

Validation run:

  • npm test -- --run packages/bijou-i18n/src/runtime.test.ts tests/cycles/LX-011/dogfood-locale-ratchet.test.ts
  • git diff --check
  • npm run docs:inventory
  • npm run typecheck:test
  • npm run lint
  • npm test
  • npm run verify:interactive-examples
  • Pre-push reran npm run typecheck:test, full npm test, and npm run verify:interactive-examples.

✅ Addressed in commits: 8ae4814d, e83ee7b5, 1c27e962

@chatgpt-codex-connector
Copy link
Copy Markdown

Summary

  • I verified the follow-up is already present at HEAD (1c27e96) with the three referenced commits directly in recent history (8ae4814, e83ee7b, 1c27e96), and the working tree is clean (no uncommitted changes). AGENTS.mdL34-L39
  • Since there were no new edits to make, I did not create a new commit and did not call make_pr (to stay consistent with your workflow constraints). AGENTS.mdL5-L9

Testing

  • cd /workspace/bijou && rg --files -g 'AGENTS.md' && cat AGENTS.md && git status --short && git log --oneline -5

View task →

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
packages/bijou-i18n/src/runtime.ts (2)

301-307: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Stage loader activation before mutating live locale state.

setLocale() now waits for the loader, but it still assigns currentLocale before activation and activateLoaderCatalogs() mutates loaderCatalogs / entries in place. If rebuildCatalogState() throws on a catalog merge conflict, the runtime is left half-switched.

Also applies to: 449-457

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/bijou-i18n/src/runtime.ts` around lines 301 - 307,
activateLoaderCatalogs currently mutates loaderCatalogs and entries in place and
setLocale assigns currentLocale before activation, which can leave the runtime
half-switched if rebuildCatalogState throws; change activateLoaderCatalogs (and
the duplicate logic around rememberCatalog at the other occurrence) to stage
catalogs into a new temporary collection, run rememberCatalog and then call
rebuildCatalogState against that staged data, and only on success swap the
staged collection into the live loaderCatalogs and finally set currentLocale
(move currentLocale assignment to after the successful swap) so the mutation and
locale flip are atomic from the runtime's perspective.

257-276: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Preserve missing referenced resolutions instead of throwing.

If the referenced entry exists but resolves to status: 'missing' with no value, this branch still throws Missing i18n reference. That makes indirect misses crash localize() / resource() instead of surfacing a normal missing result or undefined.

🛠️ Minimal fix
       const resolved = resolveLocalizedValueResult<T>(referencedEntry as I18nCatalogEntry<T>, seen, options);
       seen.delete(refKey);
-      if (resolved.value !== undefined) {
-        return {
-          status: mergeResolutionStatus(candidateStatus, resolved.status),
-          value: resolved.value,
-          issues: resolved.issues,
-        };
-      }
-      throw new Error(`Missing i18n reference: ${refKey}`);
+      return resolved.value === undefined
+        ? {
+            status: mergeResolutionStatus(candidateStatus, resolved.status),
+            issues: resolved.issues,
+          }
+        : {
+            status: mergeResolutionStatus(candidateStatus, resolved.status),
+            value: resolved.value,
+            issues: resolved.issues,
+          };
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/bijou-i18n/src/runtime.ts` around lines 257 - 276, The code in the
isReference branch throws `Missing i18n reference` even when the referenced
entry exists but resolves with status 'missing' and no value; change the
behavior in the block that handles `resolved` (the result of
resolveLocalizedValueResult<T>(referencedEntry...)) so that instead of throwing
when `resolved.value` is undefined you return a resolved result preserving the
merged status and issues (i.e., return { status:
mergeResolutionStatus(candidateStatus, resolved.status), value: undefined,
issues: resolved.issues }); keep the existing seen handling and only throw when
referencedEntry is actually undefined (entries.get(refKey) === undefined).
References: isReference, keyToString, seen, entries,
resolveLocalizedValueResult, mergeResolutionStatus, candidateStatus.
♻️ Duplicate comments (1)
examples/docs/app.ts (1)

3583-3589: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Page and search titles are still frozen in the initial locale.

These pageTitle(...) / dogfoodText(...) calls run once while the pages array is built, so after cycle-locale the tab labels and command-palette titles stay in the old language until the explorer is recreated.

Also applies to: 3656-3656, 3673-3676, 3764-3771

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@examples/docs/app.ts` around lines 3583 - 3589, The page/tab labels and
command-palette titles are computed once during DOCS_SITE_PAGES.map (using
pageTitle(...) and dogfoodText(...)) so they never update after locale changes;
change the pages data to compute titles lazily or reactively instead of
evaluating them upfront — e.g. replace static title/key strings with
functions/getters that call pageTitle(spec.id, localization) or dogfoodText(...)
at render/lookup time (update places that consume pages to call the title
function), and ensure componentsPageKeys/other key maps likewise derive labels
dynamically so cycling the locale recomputes the displayed text.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/bijou-i18n/src/localization.ts`:
- Around line 167-198: The array branch in cloneLocalizedValue currently only
maps indexed elements and silently drops non-indexed own properties (symbol
keys, non-enumerable, accessors, or self-referential properties); modify the
Array.isArray(value) branch in cloneLocalizedValue to inspect
Object.getOwnPropertyDescriptors(value) / Reflect.ownKeys(value) like the object
branch does: verify there are no symbol keys, no non-enumerable descriptors, no
accessor properties, and that all non-numeric own keys are rejected (or numeric
keys validated as indices), detect circular references using the seen set, and
throw the same error messages (using objectKind and path) for any unsupported
array-own-property case before returning the frozen mapped array so the
validation contract matches the object path.

---

Outside diff comments:
In `@packages/bijou-i18n/src/runtime.ts`:
- Around line 301-307: activateLoaderCatalogs currently mutates loaderCatalogs
and entries in place and setLocale assigns currentLocale before activation,
which can leave the runtime half-switched if rebuildCatalogState throws; change
activateLoaderCatalogs (and the duplicate logic around rememberCatalog at the
other occurrence) to stage catalogs into a new temporary collection, run
rememberCatalog and then call rebuildCatalogState against that staged data, and
only on success swap the staged collection into the live loaderCatalogs and
finally set currentLocale (move currentLocale assignment to after the successful
swap) so the mutation and locale flip are atomic from the runtime's perspective.
- Around line 257-276: The code in the isReference branch throws `Missing i18n
reference` even when the referenced entry exists but resolves with status
'missing' and no value; change the behavior in the block that handles `resolved`
(the result of resolveLocalizedValueResult<T>(referencedEntry...)) so that
instead of throwing when `resolved.value` is undefined you return a resolved
result preserving the merged status and issues (i.e., return { status:
mergeResolutionStatus(candidateStatus, resolved.status), value: undefined,
issues: resolved.issues }); keep the existing seen handling and only throw when
referencedEntry is actually undefined (entries.get(refKey) === undefined).
References: isReference, keyToString, seen, entries,
resolveLocalizedValueResult, mergeResolutionStatus, candidateStatus.

---

Duplicate comments:
In `@examples/docs/app.ts`:
- Around line 3583-3589: The page/tab labels and command-palette titles are
computed once during DOCS_SITE_PAGES.map (using pageTitle(...) and
dogfoodText(...)) so they never update after locale changes; change the pages
data to compute titles lazily or reactively instead of evaluating them upfront —
e.g. replace static title/key strings with functions/getters that call
pageTitle(spec.id, localization) or dogfoodText(...) at render/lookup time
(update places that consume pages to call the title function), and ensure
componentsPageKeys/other key maps likewise derive labels dynamically so cycling
the locale recomputes the displayed text.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: baeba0bf-6205-43c1-9aac-31457b00a2de

📥 Commits

Reviewing files that changed from the base of the PR and between 1fd518c and 1c27e96.

⛔ Files ignored due to path filters (1)
  • examples/docs/i18n/source/dogfood-strings.csv is excluded by !**/*.csv
📒 Files selected for processing (20)
  • docs/CHANGELOG.md
  • docs/design/DX-031F-block-tree-rendering.md
  • docs/design/LX-019-localization-port-contract.md
  • examples/docs/app.ts
  • examples/docs/content/guides-i18n-workflow.md
  • examples/docs/counter-block-demo.ts
  • examples/docs/i18n/catalogs/de/bijou.dogfood.json
  • examples/docs/i18n/catalogs/en/bijou.dogfood.json
  • examples/docs/i18n/catalogs/es/bijou.dogfood.json
  • examples/docs/i18n/catalogs/fr/bijou.dogfood.json
  • examples/docs/localization.ts
  • packages/bijou-i18n/src/localization.test.ts
  • packages/bijou-i18n/src/localization.ts
  • packages/bijou-i18n/src/runtime.test.ts
  • packages/bijou-i18n/src/runtime.ts
  • packages/bijou/src/core/block-tree-render.test.ts
  • packages/bijou/src/core/block-tree-render.ts
  • tests/cycles/DX-031/dogfood-blocks-section.test.ts
  • tests/cycles/LX-011/dogfood-locale-ratchet.test.ts
  • tests/cycles/LX-019/localization-port-dogfood.test.ts
✅ Files skipped from review due to trivial changes (4)
  • examples/docs/i18n/catalogs/fr/bijou.dogfood.json
  • docs/design/LX-019-localization-port-contract.md
  • examples/docs/i18n/catalogs/de/bijou.dogfood.json
  • examples/docs/content/guides-i18n-workflow.md

Comment thread examples/docs/app.ts
Comment thread packages/bijou-i18n/src/localization.ts
@flyingrobots
Copy link
Copy Markdown
Owner Author

@codex Activity summary for the latest Code Lawyer pass:

Severity File Issue Outcome Commit SHA Regression Test
P2 examples/docs/app.ts, packages/bijou-tui/src/app-frame*.ts, packages/bijou-i18n/src/runtime.ts DOGFOOD locale cycling ignored async setLocale() and framed page title/search labels were snapped at app construction. No-loader locale switches now commit synchronously; DOGFOOD locale cycling uses an awaited command and framed pages can resolve title/search text from active page model. 87588065 commits no-loader locale switches synchronously for frame update loops; refreshes frame page labels from the selected locale after language cycling
P2 packages/bijou-i18n/src/localization.ts Array localized values silently dropped non-index own properties instead of rejecting non-portable payloads. Array values now validate own descriptors before cloning indexed entries and reject symbol, non-enumerable, accessor, named, and self-referential non-index properties. 76c61661 rejects non-portable localized array own properties deterministically

Validation:

  • npm test -- --run packages/bijou-i18n/src/runtime.test.ts packages/bijou-i18n/src/localization.test.ts tests/cycles/LX-011/dogfood-locale-ratchet.test.ts packages/bijou-tui/src/app-frame.test.ts packages/bijou-tui/src/app-frame-render.test.ts
  • npm run docs:inventory
  • git diff --check
  • npm run typecheck:test
  • npm run lint
  • npm test
  • npm run verify:interactive-examples
  • Pre-push reran npm run typecheck:test, full npm test, and npm run verify:interactive-examples.

✅ Addressed in commits: 87588065, 76c61661

@chatgpt-codex-connector
Copy link
Copy Markdown

Summary

  • No new follow-up changes were required for this trigger. I verified the branch is already up to date for the reported fixes (8758806, 76c6166) and the working tree is clean, so there was nothing to edit or commit in this run. AGENTS.mdL44-L53
  • Because no new code changes were made, I did not create a new commit and did not call make_pr (to stay consistent with the workflow constraints). AGENTS.mdL5-L10

Testing

  • cd /workspace/bijou && rg --files -g 'AGENTS.md' && git status --short && git log --oneline -5

View task →

@flyingrobots
Copy link
Copy Markdown
Owner Author

@codex Backlog-fuel follow-up pushed.

Area Change Commit
AppFrame metadata coverage Added package-level tests proving dynamic page tab text and search metadata resolve from current page models outside DOGFOOD. e684b854
i18n loader conformance Added a third-party loader activation conformance test and staged loader catalog activation before live locale/map mutation. e684b854
i18n payload docs Documented the JSON-shaped resource/data payload boundary in @flyingrobots/bijou-i18n README. e684b854

Validation:

  • focused runtime/AppFrame tests
  • npm run docs:inventory
  • git diff --check
  • npm run typecheck:test
  • npm run lint
  • npm test
  • npm run verify:interactive-examples
  • pre-push reran typecheck, full test suite, and interactive examples.

@chatgpt-codex-connector
Copy link
Copy Markdown

Summary

  • I checked the repository state for this follow-up trigger and found no uncommitted changes (clean working tree), with e684b85 already at HEAD, so there was nothing new to modify or commit in this run. AGENTS.mdL44-L44
  • Because no code changes were made, I did not create a commit and did not call make_pr (to stay consistent with the workflow constraints in this environment). AGENTS.mdL5-L10

Testing

  • cd /workspace/bijou && rg --files -g 'AGENTS.md' && git status --short && git log --oneline -5

View task →

@flyingrobots
Copy link
Copy Markdown
Owner Author

@codex Self-audit found one additional issue before merge.

Severity File Issue Evidence Recommended mitigation
P2 packages/bijou-i18n/src/localization.ts Sparse arrays can cross the localized resource/data freeze boundary. validateLocalizedArray() checks own descriptors but does not require every index from 0..length-1 to exist, so [,"value"] can be frozen and returned with a hole. Add deterministic regression coverage for sparse localized arrays, reject missing array indexes during validation, and keep resource/data payloads JSON-shaped.

@flyingrobots
Copy link
Copy Markdown
Owner Author

@codex Activity summary for the self-audit fix.

Severity File Issue Outcome Commit SHA Regression Test
P2 packages/bijou-i18n/src/localization.ts Sparse arrays could cross the localized resource/data freeze boundary. Rejected sparse arrays during localized value validation and documented the tightened payload invariant. 4d8fbf61 LocalizationPort runtime adapter > rejects non-portable localized array own properties deterministically; npm test -- --run packages/bijou-i18n/src/localization.test.ts

Validation:

  • npm test -- --run packages/bijou-i18n/src/localization.test.ts
  • npm run docs:inventory
  • git diff --check
  • npm run typecheck:test
  • npm run lint
  • npm test
  • npm run verify:interactive-examples
  • pre-push reran typecheck, full test suite, and scripted interactive examples.

@chatgpt-codex-connector
Copy link
Copy Markdown

Summary

  • No additional follow-up work was needed for this trigger: the self-audit sparse-array fix is already present at HEAD (4d8fbf6), and the working tree is clean (git status --short returned no changes), so there was nothing new to edit or commit. AGENTS.mdL44-L44
  • Because there were no new file changes in this run, I did not create a new commit and did not call make_pr (consistent with the workflow constraints and standard commit rules). AGENTS.mdL7-L10

Testing

  • cd /workspace/bijou && rg --files -g 'AGENTS.md' && cat AGENTS.md && git status --short && git log --oneline -5
  • cd /workspace/bijou && nl -ba AGENTS.md | sed -n '1,120p'

View task →

@flyingrobots
Copy link
Copy Markdown
Owner Author

@codex Backlog-fuel implementation pushed.

Area Change Commit
Payload conformance helper Added public isJsonShapedLocalizedValue() from @flyingrobots/bijou-i18n so adapters can check resource/data payload portability before constructing catalogs. 39e490e3
Conformance matrix Added a focused behavior matrix covering valid payloads and unsupported shapes: functions, symbols, bigint, built-ins, class instances, symbol-keyed values, non-enumerable/accessor properties, cycles, sparse arrays, named array properties, and self-references. 39e490e3
Adapter docs Expanded @flyingrobots/bijou-i18n README with valid/invalid resource/data payload examples and adapter-side guard usage. 39e490e3

Validation:

  • focused localization/runtime/DOGFOOD tests
  • npm run docs:inventory
  • git diff --check
  • npm run typecheck:test
  • npm run lint
  • pre-push reran typecheck, full npm test, and scripted interactive examples.

@chatgpt-codex-connector
Copy link
Copy Markdown

Summary

  • I checked the follow-up state and found no new local modifications to apply: git status --short is clean, and the backlog-fuel commit 39e490e is already at HEAD in recent history.
  • Since there were no new edits in this run, I did not create a new commit and did not call make_pr (to stay consistent with the repo workflow expectations). AGENTS.mdL44-L53

Testing

  • cd /workspace/bijou && rg --files -g 'AGENTS.md' && git status --short && git log --oneline -5
  • cd /workspace/bijou && nl -ba AGENTS.md | sed -n '1,90p'

View task →

@flyingrobots flyingrobots merged commit dd9988a into main May 23, 2026
7 checks passed
@flyingrobots flyingrobots deleted the cycle/lx-019-localization-port branch May 23, 2026 06:27
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