build(step-1b): tools/check-widget-boundary.sh — Phase B4 spec §1.4 guard
Enforces the Step 1b §1.4 widget boundary invariant: widget logic
(Widget impls + layout/paint/access_node methods) lives in
crates/openpencil-shell-core/src/widgets/; shell-web's only
widget-touching file is `widget_host.rs` and even there the only
widget-method signature allowed is the `// glue:` marked paint
dispatcher.
Forward checks (no widget logic in shell-web/src/):
- F1: `impl(<...>)?[[:space:]]+(ns::)*Widget[[:space:]]+for[[:space:]]`
anywhere under shell-web/src/. Allows generic params + arbitrary
namespace depth so `impl<T> shell_core::widgets::Widget for X` is
caught. No `// glue:` exemption — Widget impls have no place in
shell-web period.
- F2: `fn[[:space:]]+(layout|access_node)\(` anywhere under
shell-web/src/. No exemption.
- F3: `fn[[:space:]]+paint\(` under shell-web/src/, EXCEPT lines in
widget_host.rs that ALSO carry `// glue:`. Tight exemption — the
marker only blesses one specific signature, not arbitrary tagged
lines.
- F4: any line under shell-web/src/ mentioning both
`openpencil_shell_core` AND `widgets`, except widget_host.rs.
Catches direct + grouped `use` forms (e.g. `use
openpencil_shell_core::{widgets::TreeWidget};`) plus path
expressions. Multi-line braced `use` is out of scope (single-line
policy in this crate).
Reverse check (shell-core/src/widgets/ has all four impls):
- R1: For each of {tree, prop_row, dropdown, text_input}, the file
must exist AND, after stripping `//` line comments, must contain
a live `impl Widget for X`. Block comments out of scope (line
comments only in this directory).
CI integration:
- New "Verify Step 1b widget boundary (spec §1.4)" step in
.github/workflows/rust-check.yml right after the existing
"Verify Jian boundary invariants" step, gated to Linux runner
(matches the jian-boundaries pattern).
- Added `tools/check-jian-boundaries.sh` and
`tools/check-widget-boundary.sh` to the rust-check.yml push +
pull_request path filters so PRs editing only the checker still
trigger CI.
7-test regression matrix (positive + 6 negative cases):
- positive (real codebase) → PASS
- generic `impl<T> Widget for X` injected → FAIL F1
- direct `use openpencil_shell_core::widgets` outside host → FAIL F4
- `// glue:` tag on `impl Widget for X` line → FAIL F1 (exemption
doesn't save it; only `fn paint` lines are exempted)
- shell-core file replaced with `// stub` → FAIL R1
- grouped `use openpencil_shell_core::{widgets::TreeWidget};` → FAIL F4
- grouped `use openpencil_shell_core::{widgets};` → FAIL F4
- shell-core file body replaced with `// impl Widget for X { ... }` → FAIL R1
Codex iterate review: 5 rounds → GO. Round 1 BLOCK (greedy
WidgetHost match), R2 BLOCK + 3 CONCERN (calls/imports unchecked,
generic impls, broad exemption, filename-only count), R3 BLOCK +
CONCERN (grouped imports, commented-out impls), R4 2 NITs
(documentation parity), R5 GO clean.
2026-05-09 13:48:00 +00:00
#!/usr/bin/env bash
# tools/check-widget-boundary.sh — Step 1b §1.4 widget boundary invariant.
#
# Per spec §1.4: widget logic (Widget impls, layout/paint/access_node)
refactor: extract op-editor-ui crate
Relocate the widget facade (widgets/, including the Widget trait,
render primitives, the editor-UI compositions, the CanvasViewport
center canvas, the lucide icon drawer, and editor_state_ext), the
theme tokens, the layout-resolved render scene (layout_scene +
layout_scene_hit), and the design-variable aggregation (scene_vars)
out of openpencil-shell-core into a dedicated op-editor-ui crate.
The canvas widgets (canvas_viewport*) stay inside op-editor-ui rather
than splitting into a separate op-canvas crate: they depend on the
widgets/ siblings editor_state_ext + icons and on the Widget trait, so
a clean mechanical split is not possible — per the task's explicit
allowance not to force a fragile split.
op-editor-ui's lib.rs mirrors the old shell-core crate-root re-exports
(render_backend facade types + jian gesture types + the i18n alias) so
every intra-module `crate::Color` / `crate::theme` / `crate::widgets`
path resolves unchanged — a pure relocation with no path rewrites
inside the moved modules. openpencil-shell-core becomes a thin
re-export shim (`pub use op_editor_ui::{widgets, theme, ...}`) so the
hosts keep resolving `openpencil_shell_core::*` until the Task 7.3
host rename dissolves the crate. The widgets_static integration test
moves to op-editor-ui/tests with its imports rewritten. The widget
boundary script's reverse-check path is updated to the new crate.
No behaviour change; all tests move with their code.
2026-05-16 15:33:47 +00:00
# lives in `crates/op-editor-ui/src/widgets/` (Phase 7 reorg moved it
2026-05-16 16:01:35 +00:00
# out of the old openpencil-shell-core). The op-host-web crate's
2026-05-11 13:30:06 +00:00
# widget glue is scoped to a single module — `widget_host.rs` plus
# its sibling submodules under `widget_host/` (spec amendment
# 2026-05-11: the original "one file" constraint conflicted with
# the 800-line file cap once the host grew real keyboard /
# clipboard / paint coordination, so the module is now allowed to
# span multiple sibling files inside the `widget_host/` directory).
# The `// glue:` marker on the paint signature is still the only
# `fn paint(` allowed to drive widgets from this crate.
2026-05-16 16:01:35 +00:00
# Any function or file in op-host-web that pulls
# `op_editor_ui::widgets::*` must live in the widget_host
# module (the spec's "any function pulling the widget facade" clause).
build(step-1b): tools/check-widget-boundary.sh — Phase B4 spec §1.4 guard
Enforces the Step 1b §1.4 widget boundary invariant: widget logic
(Widget impls + layout/paint/access_node methods) lives in
crates/openpencil-shell-core/src/widgets/; shell-web's only
widget-touching file is `widget_host.rs` and even there the only
widget-method signature allowed is the `// glue:` marked paint
dispatcher.
Forward checks (no widget logic in shell-web/src/):
- F1: `impl(<...>)?[[:space:]]+(ns::)*Widget[[:space:]]+for[[:space:]]`
anywhere under shell-web/src/. Allows generic params + arbitrary
namespace depth so `impl<T> shell_core::widgets::Widget for X` is
caught. No `// glue:` exemption — Widget impls have no place in
shell-web period.
- F2: `fn[[:space:]]+(layout|access_node)\(` anywhere under
shell-web/src/. No exemption.
- F3: `fn[[:space:]]+paint\(` under shell-web/src/, EXCEPT lines in
widget_host.rs that ALSO carry `// glue:`. Tight exemption — the
marker only blesses one specific signature, not arbitrary tagged
lines.
- F4: any line under shell-web/src/ mentioning both
`openpencil_shell_core` AND `widgets`, except widget_host.rs.
Catches direct + grouped `use` forms (e.g. `use
openpencil_shell_core::{widgets::TreeWidget};`) plus path
expressions. Multi-line braced `use` is out of scope (single-line
policy in this crate).
Reverse check (shell-core/src/widgets/ has all four impls):
- R1: For each of {tree, prop_row, dropdown, text_input}, the file
must exist AND, after stripping `//` line comments, must contain
a live `impl Widget for X`. Block comments out of scope (line
comments only in this directory).
CI integration:
- New "Verify Step 1b widget boundary (spec §1.4)" step in
.github/workflows/rust-check.yml right after the existing
"Verify Jian boundary invariants" step, gated to Linux runner
(matches the jian-boundaries pattern).
- Added `tools/check-jian-boundaries.sh` and
`tools/check-widget-boundary.sh` to the rust-check.yml push +
pull_request path filters so PRs editing only the checker still
trigger CI.
7-test regression matrix (positive + 6 negative cases):
- positive (real codebase) → PASS
- generic `impl<T> Widget for X` injected → FAIL F1
- direct `use openpencil_shell_core::widgets` outside host → FAIL F4
- `// glue:` tag on `impl Widget for X` line → FAIL F1 (exemption
doesn't save it; only `fn paint` lines are exempted)
- shell-core file replaced with `// stub` → FAIL R1
- grouped `use openpencil_shell_core::{widgets::TreeWidget};` → FAIL F4
- grouped `use openpencil_shell_core::{widgets};` → FAIL F4
- shell-core file body replaced with `// impl Widget for X { ... }` → FAIL R1
Codex iterate review: 5 rounds → GO. Round 1 BLOCK (greedy
WidgetHost match), R2 BLOCK + 3 CONCERN (calls/imports unchecked,
generic impls, broad exemption, filename-only count), R3 BLOCK +
CONCERN (grouped imports, commented-out impls), R4 2 NITs
(documentation parity), R5 GO clean.
2026-05-09 13:48:00 +00:00
#
refactor: extract op-editor-ui crate
Relocate the widget facade (widgets/, including the Widget trait,
render primitives, the editor-UI compositions, the CanvasViewport
center canvas, the lucide icon drawer, and editor_state_ext), the
theme tokens, the layout-resolved render scene (layout_scene +
layout_scene_hit), and the design-variable aggregation (scene_vars)
out of openpencil-shell-core into a dedicated op-editor-ui crate.
The canvas widgets (canvas_viewport*) stay inside op-editor-ui rather
than splitting into a separate op-canvas crate: they depend on the
widgets/ siblings editor_state_ext + icons and on the Widget trait, so
a clean mechanical split is not possible — per the task's explicit
allowance not to force a fragile split.
op-editor-ui's lib.rs mirrors the old shell-core crate-root re-exports
(render_backend facade types + jian gesture types + the i18n alias) so
every intra-module `crate::Color` / `crate::theme` / `crate::widgets`
path resolves unchanged — a pure relocation with no path rewrites
inside the moved modules. openpencil-shell-core becomes a thin
re-export shim (`pub use op_editor_ui::{widgets, theme, ...}`) so the
hosts keep resolving `openpencil_shell_core::*` until the Task 7.3
host rename dissolves the crate. The widgets_static integration test
moves to op-editor-ui/tests with its imports rewritten. The widget
boundary script's reverse-check path is updated to the new crate.
No behaviour change; all tests move with their code.
2026-05-16 15:33:47 +00:00
# Reverse direction: op-editor-ui/src/widgets/ MUST contain four impl
build(step-1b): tools/check-widget-boundary.sh — Phase B4 spec §1.4 guard
Enforces the Step 1b §1.4 widget boundary invariant: widget logic
(Widget impls + layout/paint/access_node methods) lives in
crates/openpencil-shell-core/src/widgets/; shell-web's only
widget-touching file is `widget_host.rs` and even there the only
widget-method signature allowed is the `// glue:` marked paint
dispatcher.
Forward checks (no widget logic in shell-web/src/):
- F1: `impl(<...>)?[[:space:]]+(ns::)*Widget[[:space:]]+for[[:space:]]`
anywhere under shell-web/src/. Allows generic params + arbitrary
namespace depth so `impl<T> shell_core::widgets::Widget for X` is
caught. No `// glue:` exemption — Widget impls have no place in
shell-web period.
- F2: `fn[[:space:]]+(layout|access_node)\(` anywhere under
shell-web/src/. No exemption.
- F3: `fn[[:space:]]+paint\(` under shell-web/src/, EXCEPT lines in
widget_host.rs that ALSO carry `// glue:`. Tight exemption — the
marker only blesses one specific signature, not arbitrary tagged
lines.
- F4: any line under shell-web/src/ mentioning both
`openpencil_shell_core` AND `widgets`, except widget_host.rs.
Catches direct + grouped `use` forms (e.g. `use
openpencil_shell_core::{widgets::TreeWidget};`) plus path
expressions. Multi-line braced `use` is out of scope (single-line
policy in this crate).
Reverse check (shell-core/src/widgets/ has all four impls):
- R1: For each of {tree, prop_row, dropdown, text_input}, the file
must exist AND, after stripping `//` line comments, must contain
a live `impl Widget for X`. Block comments out of scope (line
comments only in this directory).
CI integration:
- New "Verify Step 1b widget boundary (spec §1.4)" step in
.github/workflows/rust-check.yml right after the existing
"Verify Jian boundary invariants" step, gated to Linux runner
(matches the jian-boundaries pattern).
- Added `tools/check-jian-boundaries.sh` and
`tools/check-widget-boundary.sh` to the rust-check.yml push +
pull_request path filters so PRs editing only the checker still
trigger CI.
7-test regression matrix (positive + 6 negative cases):
- positive (real codebase) → PASS
- generic `impl<T> Widget for X` injected → FAIL F1
- direct `use openpencil_shell_core::widgets` outside host → FAIL F4
- `// glue:` tag on `impl Widget for X` line → FAIL F1 (exemption
doesn't save it; only `fn paint` lines are exempted)
- shell-core file replaced with `// stub` → FAIL R1
- grouped `use openpencil_shell_core::{widgets::TreeWidget};` → FAIL F4
- grouped `use openpencil_shell_core::{widgets};` → FAIL F4
- shell-core file body replaced with `// impl Widget for X { ... }` → FAIL R1
Codex iterate review: 5 rounds → GO. Round 1 BLOCK (greedy
WidgetHost match), R2 BLOCK + 3 CONCERN (calls/imports unchecked,
generic impls, broad exemption, filename-only count), R3 BLOCK +
CONCERN (grouped imports, commented-out impls), R4 2 NITs
(documentation parity), R5 GO clean.
2026-05-09 13:48:00 +00:00
# files (tree, prop_row, dropdown, text_input) AND each file must
# carry a real `impl Widget for X` — a stale file with the impl
2026-05-16 16:01:35 +00:00
# removed must not silently pass. (Phase 7.3 reorg: the
# openpencil-shell-core re-export shim was dissolved; op-host-web
# now pulls `op_editor_ui::widgets` directly from the real source
# crate, so the forward F4 path is unchanged.)
build(step-1b): tools/check-widget-boundary.sh — Phase B4 spec §1.4 guard
Enforces the Step 1b §1.4 widget boundary invariant: widget logic
(Widget impls + layout/paint/access_node methods) lives in
crates/openpencil-shell-core/src/widgets/; shell-web's only
widget-touching file is `widget_host.rs` and even there the only
widget-method signature allowed is the `// glue:` marked paint
dispatcher.
Forward checks (no widget logic in shell-web/src/):
- F1: `impl(<...>)?[[:space:]]+(ns::)*Widget[[:space:]]+for[[:space:]]`
anywhere under shell-web/src/. Allows generic params + arbitrary
namespace depth so `impl<T> shell_core::widgets::Widget for X` is
caught. No `// glue:` exemption — Widget impls have no place in
shell-web period.
- F2: `fn[[:space:]]+(layout|access_node)\(` anywhere under
shell-web/src/. No exemption.
- F3: `fn[[:space:]]+paint\(` under shell-web/src/, EXCEPT lines in
widget_host.rs that ALSO carry `// glue:`. Tight exemption — the
marker only blesses one specific signature, not arbitrary tagged
lines.
- F4: any line under shell-web/src/ mentioning both
`openpencil_shell_core` AND `widgets`, except widget_host.rs.
Catches direct + grouped `use` forms (e.g. `use
openpencil_shell_core::{widgets::TreeWidget};`) plus path
expressions. Multi-line braced `use` is out of scope (single-line
policy in this crate).
Reverse check (shell-core/src/widgets/ has all four impls):
- R1: For each of {tree, prop_row, dropdown, text_input}, the file
must exist AND, after stripping `//` line comments, must contain
a live `impl Widget for X`. Block comments out of scope (line
comments only in this directory).
CI integration:
- New "Verify Step 1b widget boundary (spec §1.4)" step in
.github/workflows/rust-check.yml right after the existing
"Verify Jian boundary invariants" step, gated to Linux runner
(matches the jian-boundaries pattern).
- Added `tools/check-jian-boundaries.sh` and
`tools/check-widget-boundary.sh` to the rust-check.yml push +
pull_request path filters so PRs editing only the checker still
trigger CI.
7-test regression matrix (positive + 6 negative cases):
- positive (real codebase) → PASS
- generic `impl<T> Widget for X` injected → FAIL F1
- direct `use openpencil_shell_core::widgets` outside host → FAIL F4
- `// glue:` tag on `impl Widget for X` line → FAIL F1 (exemption
doesn't save it; only `fn paint` lines are exempted)
- shell-core file replaced with `// stub` → FAIL R1
- grouped `use openpencil_shell_core::{widgets::TreeWidget};` → FAIL F4
- grouped `use openpencil_shell_core::{widgets};` → FAIL F4
- shell-core file body replaced with `// impl Widget for X { ... }` → FAIL R1
Codex iterate review: 5 rounds → GO. Round 1 BLOCK (greedy
WidgetHost match), R2 BLOCK + 3 CONCERN (calls/imports unchecked,
generic impls, broad exemption, filename-only count), R3 BLOCK +
CONCERN (grouped imports, commented-out impls), R4 2 NITs
(documentation parity), R5 GO clean.
2026-05-09 13:48:00 +00:00
#
# Exit semantics:
# 0 PASS — both invariants hold.
2026-05-16 16:01:35 +00:00
# 1 FAIL — widget logic leaked into op-host-web OR the
# op-editor-ui widget impl files are missing / empty.
build(step-1b): tools/check-widget-boundary.sh — Phase B4 spec §1.4 guard
Enforces the Step 1b §1.4 widget boundary invariant: widget logic
(Widget impls + layout/paint/access_node methods) lives in
crates/openpencil-shell-core/src/widgets/; shell-web's only
widget-touching file is `widget_host.rs` and even there the only
widget-method signature allowed is the `// glue:` marked paint
dispatcher.
Forward checks (no widget logic in shell-web/src/):
- F1: `impl(<...>)?[[:space:]]+(ns::)*Widget[[:space:]]+for[[:space:]]`
anywhere under shell-web/src/. Allows generic params + arbitrary
namespace depth so `impl<T> shell_core::widgets::Widget for X` is
caught. No `// glue:` exemption — Widget impls have no place in
shell-web period.
- F2: `fn[[:space:]]+(layout|access_node)\(` anywhere under
shell-web/src/. No exemption.
- F3: `fn[[:space:]]+paint\(` under shell-web/src/, EXCEPT lines in
widget_host.rs that ALSO carry `// glue:`. Tight exemption — the
marker only blesses one specific signature, not arbitrary tagged
lines.
- F4: any line under shell-web/src/ mentioning both
`openpencil_shell_core` AND `widgets`, except widget_host.rs.
Catches direct + grouped `use` forms (e.g. `use
openpencil_shell_core::{widgets::TreeWidget};`) plus path
expressions. Multi-line braced `use` is out of scope (single-line
policy in this crate).
Reverse check (shell-core/src/widgets/ has all four impls):
- R1: For each of {tree, prop_row, dropdown, text_input}, the file
must exist AND, after stripping `//` line comments, must contain
a live `impl Widget for X`. Block comments out of scope (line
comments only in this directory).
CI integration:
- New "Verify Step 1b widget boundary (spec §1.4)" step in
.github/workflows/rust-check.yml right after the existing
"Verify Jian boundary invariants" step, gated to Linux runner
(matches the jian-boundaries pattern).
- Added `tools/check-jian-boundaries.sh` and
`tools/check-widget-boundary.sh` to the rust-check.yml push +
pull_request path filters so PRs editing only the checker still
trigger CI.
7-test regression matrix (positive + 6 negative cases):
- positive (real codebase) → PASS
- generic `impl<T> Widget for X` injected → FAIL F1
- direct `use openpencil_shell_core::widgets` outside host → FAIL F4
- `// glue:` tag on `impl Widget for X` line → FAIL F1 (exemption
doesn't save it; only `fn paint` lines are exempted)
- shell-core file replaced with `// stub` → FAIL R1
- grouped `use openpencil_shell_core::{widgets::TreeWidget};` → FAIL F4
- grouped `use openpencil_shell_core::{widgets};` → FAIL F4
- shell-core file body replaced with `// impl Widget for X { ... }` → FAIL R1
Codex iterate review: 5 rounds → GO. Round 1 BLOCK (greedy
WidgetHost match), R2 BLOCK + 3 CONCERN (calls/imports unchecked,
generic impls, broad exemption, filename-only count), R3 BLOCK +
CONCERN (grouped imports, commented-out impls), R4 2 NITs
(documentation parity), R5 GO clean.
2026-05-09 13:48:00 +00:00
set -euo pipefail
2026-05-16 16:01:35 +00:00
WEB_SRC = "crates/op-host-web/src"
refactor: extract op-editor-ui crate
Relocate the widget facade (widgets/, including the Widget trait,
render primitives, the editor-UI compositions, the CanvasViewport
center canvas, the lucide icon drawer, and editor_state_ext), the
theme tokens, the layout-resolved render scene (layout_scene +
layout_scene_hit), and the design-variable aggregation (scene_vars)
out of openpencil-shell-core into a dedicated op-editor-ui crate.
The canvas widgets (canvas_viewport*) stay inside op-editor-ui rather
than splitting into a separate op-canvas crate: they depend on the
widgets/ siblings editor_state_ext + icons and on the Widget trait, so
a clean mechanical split is not possible — per the task's explicit
allowance not to force a fragile split.
op-editor-ui's lib.rs mirrors the old shell-core crate-root re-exports
(render_backend facade types + jian gesture types + the i18n alias) so
every intra-module `crate::Color` / `crate::theme` / `crate::widgets`
path resolves unchanged — a pure relocation with no path rewrites
inside the moved modules. openpencil-shell-core becomes a thin
re-export shim (`pub use op_editor_ui::{widgets, theme, ...}`) so the
hosts keep resolving `openpencil_shell_core::*` until the Task 7.3
host rename dissolves the crate. The widgets_static integration test
moves to op-editor-ui/tests with its imports rewritten. The widget
boundary script's reverse-check path is updated to the new crate.
No behaviour change; all tests move with their code.
2026-05-16 15:33:47 +00:00
CORE_WIDGETS = "crates/op-editor-ui/src/widgets"
build(step-1b): tools/check-widget-boundary.sh — Phase B4 spec §1.4 guard
Enforces the Step 1b §1.4 widget boundary invariant: widget logic
(Widget impls + layout/paint/access_node methods) lives in
crates/openpencil-shell-core/src/widgets/; shell-web's only
widget-touching file is `widget_host.rs` and even there the only
widget-method signature allowed is the `// glue:` marked paint
dispatcher.
Forward checks (no widget logic in shell-web/src/):
- F1: `impl(<...>)?[[:space:]]+(ns::)*Widget[[:space:]]+for[[:space:]]`
anywhere under shell-web/src/. Allows generic params + arbitrary
namespace depth so `impl<T> shell_core::widgets::Widget for X` is
caught. No `// glue:` exemption — Widget impls have no place in
shell-web period.
- F2: `fn[[:space:]]+(layout|access_node)\(` anywhere under
shell-web/src/. No exemption.
- F3: `fn[[:space:]]+paint\(` under shell-web/src/, EXCEPT lines in
widget_host.rs that ALSO carry `// glue:`. Tight exemption — the
marker only blesses one specific signature, not arbitrary tagged
lines.
- F4: any line under shell-web/src/ mentioning both
`openpencil_shell_core` AND `widgets`, except widget_host.rs.
Catches direct + grouped `use` forms (e.g. `use
openpencil_shell_core::{widgets::TreeWidget};`) plus path
expressions. Multi-line braced `use` is out of scope (single-line
policy in this crate).
Reverse check (shell-core/src/widgets/ has all four impls):
- R1: For each of {tree, prop_row, dropdown, text_input}, the file
must exist AND, after stripping `//` line comments, must contain
a live `impl Widget for X`. Block comments out of scope (line
comments only in this directory).
CI integration:
- New "Verify Step 1b widget boundary (spec §1.4)" step in
.github/workflows/rust-check.yml right after the existing
"Verify Jian boundary invariants" step, gated to Linux runner
(matches the jian-boundaries pattern).
- Added `tools/check-jian-boundaries.sh` and
`tools/check-widget-boundary.sh` to the rust-check.yml push +
pull_request path filters so PRs editing only the checker still
trigger CI.
7-test regression matrix (positive + 6 negative cases):
- positive (real codebase) → PASS
- generic `impl<T> Widget for X` injected → FAIL F1
- direct `use openpencil_shell_core::widgets` outside host → FAIL F4
- `// glue:` tag on `impl Widget for X` line → FAIL F1 (exemption
doesn't save it; only `fn paint` lines are exempted)
- shell-core file replaced with `// stub` → FAIL R1
- grouped `use openpencil_shell_core::{widgets::TreeWidget};` → FAIL F4
- grouped `use openpencil_shell_core::{widgets};` → FAIL F4
- shell-core file body replaced with `// impl Widget for X { ... }` → FAIL R1
Codex iterate review: 5 rounds → GO. Round 1 BLOCK (greedy
WidgetHost match), R2 BLOCK + 3 CONCERN (calls/imports unchecked,
generic impls, broad exemption, filename-only count), R3 BLOCK +
CONCERN (grouped imports, commented-out impls), R4 2 NITs
(documentation parity), R5 GO clean.
2026-05-09 13:48:00 +00:00
fail_lines = ( )
# ---------------------------------------------------------------------
# Forward F1: no `impl Widget for X` (with optional generic params and
2026-05-16 16:01:35 +00:00
# arbitrary namespace segments) anywhere under op-host-web/src/.
build(step-1b): tools/check-widget-boundary.sh — Phase B4 spec §1.4 guard
Enforces the Step 1b §1.4 widget boundary invariant: widget logic
(Widget impls + layout/paint/access_node methods) lives in
crates/openpencil-shell-core/src/widgets/; shell-web's only
widget-touching file is `widget_host.rs` and even there the only
widget-method signature allowed is the `// glue:` marked paint
dispatcher.
Forward checks (no widget logic in shell-web/src/):
- F1: `impl(<...>)?[[:space:]]+(ns::)*Widget[[:space:]]+for[[:space:]]`
anywhere under shell-web/src/. Allows generic params + arbitrary
namespace depth so `impl<T> shell_core::widgets::Widget for X` is
caught. No `// glue:` exemption — Widget impls have no place in
shell-web period.
- F2: `fn[[:space:]]+(layout|access_node)\(` anywhere under
shell-web/src/. No exemption.
- F3: `fn[[:space:]]+paint\(` under shell-web/src/, EXCEPT lines in
widget_host.rs that ALSO carry `// glue:`. Tight exemption — the
marker only blesses one specific signature, not arbitrary tagged
lines.
- F4: any line under shell-web/src/ mentioning both
`openpencil_shell_core` AND `widgets`, except widget_host.rs.
Catches direct + grouped `use` forms (e.g. `use
openpencil_shell_core::{widgets::TreeWidget};`) plus path
expressions. Multi-line braced `use` is out of scope (single-line
policy in this crate).
Reverse check (shell-core/src/widgets/ has all four impls):
- R1: For each of {tree, prop_row, dropdown, text_input}, the file
must exist AND, after stripping `//` line comments, must contain
a live `impl Widget for X`. Block comments out of scope (line
comments only in this directory).
CI integration:
- New "Verify Step 1b widget boundary (spec §1.4)" step in
.github/workflows/rust-check.yml right after the existing
"Verify Jian boundary invariants" step, gated to Linux runner
(matches the jian-boundaries pattern).
- Added `tools/check-jian-boundaries.sh` and
`tools/check-widget-boundary.sh` to the rust-check.yml push +
pull_request path filters so PRs editing only the checker still
trigger CI.
7-test regression matrix (positive + 6 negative cases):
- positive (real codebase) → PASS
- generic `impl<T> Widget for X` injected → FAIL F1
- direct `use openpencil_shell_core::widgets` outside host → FAIL F4
- `// glue:` tag on `impl Widget for X` line → FAIL F1 (exemption
doesn't save it; only `fn paint` lines are exempted)
- shell-core file replaced with `// stub` → FAIL R1
- grouped `use openpencil_shell_core::{widgets::TreeWidget};` → FAIL F4
- grouped `use openpencil_shell_core::{widgets};` → FAIL F4
- shell-core file body replaced with `// impl Widget for X { ... }` → FAIL R1
Codex iterate review: 5 rounds → GO. Round 1 BLOCK (greedy
WidgetHost match), R2 BLOCK + 3 CONCERN (calls/imports unchecked,
generic impls, broad exemption, filename-only count), R3 BLOCK +
CONCERN (grouped imports, commented-out impls), R4 2 NITs
(documentation parity), R5 GO clean.
2026-05-09 13:48:00 +00:00
# Even widget_host.rs is not allowed to host a Widget impl — all real
2026-05-16 16:01:35 +00:00
# impls live in op-editor-ui. The `// glue:` exemption is for the paint
build(step-1b): tools/check-widget-boundary.sh — Phase B4 spec §1.4 guard
Enforces the Step 1b §1.4 widget boundary invariant: widget logic
(Widget impls + layout/paint/access_node methods) lives in
crates/openpencil-shell-core/src/widgets/; shell-web's only
widget-touching file is `widget_host.rs` and even there the only
widget-method signature allowed is the `// glue:` marked paint
dispatcher.
Forward checks (no widget logic in shell-web/src/):
- F1: `impl(<...>)?[[:space:]]+(ns::)*Widget[[:space:]]+for[[:space:]]`
anywhere under shell-web/src/. Allows generic params + arbitrary
namespace depth so `impl<T> shell_core::widgets::Widget for X` is
caught. No `// glue:` exemption — Widget impls have no place in
shell-web period.
- F2: `fn[[:space:]]+(layout|access_node)\(` anywhere under
shell-web/src/. No exemption.
- F3: `fn[[:space:]]+paint\(` under shell-web/src/, EXCEPT lines in
widget_host.rs that ALSO carry `// glue:`. Tight exemption — the
marker only blesses one specific signature, not arbitrary tagged
lines.
- F4: any line under shell-web/src/ mentioning both
`openpencil_shell_core` AND `widgets`, except widget_host.rs.
Catches direct + grouped `use` forms (e.g. `use
openpencil_shell_core::{widgets::TreeWidget};`) plus path
expressions. Multi-line braced `use` is out of scope (single-line
policy in this crate).
Reverse check (shell-core/src/widgets/ has all four impls):
- R1: For each of {tree, prop_row, dropdown, text_input}, the file
must exist AND, after stripping `//` line comments, must contain
a live `impl Widget for X`. Block comments out of scope (line
comments only in this directory).
CI integration:
- New "Verify Step 1b widget boundary (spec §1.4)" step in
.github/workflows/rust-check.yml right after the existing
"Verify Jian boundary invariants" step, gated to Linux runner
(matches the jian-boundaries pattern).
- Added `tools/check-jian-boundaries.sh` and
`tools/check-widget-boundary.sh` to the rust-check.yml push +
pull_request path filters so PRs editing only the checker still
trigger CI.
7-test regression matrix (positive + 6 negative cases):
- positive (real codebase) → PASS
- generic `impl<T> Widget for X` injected → FAIL F1
- direct `use openpencil_shell_core::widgets` outside host → FAIL F4
- `// glue:` tag on `impl Widget for X` line → FAIL F1 (exemption
doesn't save it; only `fn paint` lines are exempted)
- shell-core file replaced with `// stub` → FAIL R1
- grouped `use openpencil_shell_core::{widgets::TreeWidget};` → FAIL F4
- grouped `use openpencil_shell_core::{widgets};` → FAIL F4
- shell-core file body replaced with `// impl Widget for X { ... }` → FAIL R1
Codex iterate review: 5 rounds → GO. Round 1 BLOCK (greedy
WidgetHost match), R2 BLOCK + 3 CONCERN (calls/imports unchecked,
generic impls, broad exemption, filename-only count), R3 BLOCK +
CONCERN (grouped imports, commented-out impls), R4 2 NITs
(documentation parity), R5 GO clean.
2026-05-09 13:48:00 +00:00
# signature only, not for Widget trait impls (codex B4 R2 CONCERN-2).
# ---------------------------------------------------------------------
# `impl(<...>)?[[:space:]]+(ns::)*Widget[[:space:]]+for[[:space:]]`:
# - `(<[^>]+>)?` allows zero-or-one generic param block (`impl<T>`)
# before the trait path (codex B4 R2 CONCERN-1).
# - `([[:alnum:]_]+::)*` allows zero or more namespace segments
# before `Widget` (codex B4 R1 BLOCK fix).
# - The `[[:space:]]+for[[:space:]]` tail rules out structs named
# WidgetHost / WidgetId.
impl_hits = " $( grep -RInE \
'impl(<[^>]+>)?[[:space:]]+([[:alnum:]_]+::)*Widget[[:space:]]+for[[:space:]]' \
" ${ WEB_SRC } " 2>/dev/null || true ) "
if [ -n " ${ impl_hits } " ] ; then
2026-05-16 16:01:35 +00:00
fail_lines += ( "Forward F1: Widget trait impl in op-host-web (must live in op-editor-ui):" " ${ impl_hits } " )
build(step-1b): tools/check-widget-boundary.sh — Phase B4 spec §1.4 guard
Enforces the Step 1b §1.4 widget boundary invariant: widget logic
(Widget impls + layout/paint/access_node methods) lives in
crates/openpencil-shell-core/src/widgets/; shell-web's only
widget-touching file is `widget_host.rs` and even there the only
widget-method signature allowed is the `// glue:` marked paint
dispatcher.
Forward checks (no widget logic in shell-web/src/):
- F1: `impl(<...>)?[[:space:]]+(ns::)*Widget[[:space:]]+for[[:space:]]`
anywhere under shell-web/src/. Allows generic params + arbitrary
namespace depth so `impl<T> shell_core::widgets::Widget for X` is
caught. No `// glue:` exemption — Widget impls have no place in
shell-web period.
- F2: `fn[[:space:]]+(layout|access_node)\(` anywhere under
shell-web/src/. No exemption.
- F3: `fn[[:space:]]+paint\(` under shell-web/src/, EXCEPT lines in
widget_host.rs that ALSO carry `// glue:`. Tight exemption — the
marker only blesses one specific signature, not arbitrary tagged
lines.
- F4: any line under shell-web/src/ mentioning both
`openpencil_shell_core` AND `widgets`, except widget_host.rs.
Catches direct + grouped `use` forms (e.g. `use
openpencil_shell_core::{widgets::TreeWidget};`) plus path
expressions. Multi-line braced `use` is out of scope (single-line
policy in this crate).
Reverse check (shell-core/src/widgets/ has all four impls):
- R1: For each of {tree, prop_row, dropdown, text_input}, the file
must exist AND, after stripping `//` line comments, must contain
a live `impl Widget for X`. Block comments out of scope (line
comments only in this directory).
CI integration:
- New "Verify Step 1b widget boundary (spec §1.4)" step in
.github/workflows/rust-check.yml right after the existing
"Verify Jian boundary invariants" step, gated to Linux runner
(matches the jian-boundaries pattern).
- Added `tools/check-jian-boundaries.sh` and
`tools/check-widget-boundary.sh` to the rust-check.yml push +
pull_request path filters so PRs editing only the checker still
trigger CI.
7-test regression matrix (positive + 6 negative cases):
- positive (real codebase) → PASS
- generic `impl<T> Widget for X` injected → FAIL F1
- direct `use openpencil_shell_core::widgets` outside host → FAIL F4
- `// glue:` tag on `impl Widget for X` line → FAIL F1 (exemption
doesn't save it; only `fn paint` lines are exempted)
- shell-core file replaced with `// stub` → FAIL R1
- grouped `use openpencil_shell_core::{widgets::TreeWidget};` → FAIL F4
- grouped `use openpencil_shell_core::{widgets};` → FAIL F4
- shell-core file body replaced with `// impl Widget for X { ... }` → FAIL R1
Codex iterate review: 5 rounds → GO. Round 1 BLOCK (greedy
WidgetHost match), R2 BLOCK + 3 CONCERN (calls/imports unchecked,
generic impls, broad exemption, filename-only count), R3 BLOCK +
CONCERN (grouped imports, commented-out impls), R4 2 NITs
(documentation parity), R5 GO clean.
2026-05-09 13:48:00 +00:00
fi
# ---------------------------------------------------------------------
# Forward F2: no `fn layout(` / `fn access_node(` anywhere under
2026-05-16 16:01:35 +00:00
# op-host-web/src/. These are widget-trait method signatures and have
build(step-1b): tools/check-widget-boundary.sh — Phase B4 spec §1.4 guard
Enforces the Step 1b §1.4 widget boundary invariant: widget logic
(Widget impls + layout/paint/access_node methods) lives in
crates/openpencil-shell-core/src/widgets/; shell-web's only
widget-touching file is `widget_host.rs` and even there the only
widget-method signature allowed is the `// glue:` marked paint
dispatcher.
Forward checks (no widget logic in shell-web/src/):
- F1: `impl(<...>)?[[:space:]]+(ns::)*Widget[[:space:]]+for[[:space:]]`
anywhere under shell-web/src/. Allows generic params + arbitrary
namespace depth so `impl<T> shell_core::widgets::Widget for X` is
caught. No `// glue:` exemption — Widget impls have no place in
shell-web period.
- F2: `fn[[:space:]]+(layout|access_node)\(` anywhere under
shell-web/src/. No exemption.
- F3: `fn[[:space:]]+paint\(` under shell-web/src/, EXCEPT lines in
widget_host.rs that ALSO carry `// glue:`. Tight exemption — the
marker only blesses one specific signature, not arbitrary tagged
lines.
- F4: any line under shell-web/src/ mentioning both
`openpencil_shell_core` AND `widgets`, except widget_host.rs.
Catches direct + grouped `use` forms (e.g. `use
openpencil_shell_core::{widgets::TreeWidget};`) plus path
expressions. Multi-line braced `use` is out of scope (single-line
policy in this crate).
Reverse check (shell-core/src/widgets/ has all four impls):
- R1: For each of {tree, prop_row, dropdown, text_input}, the file
must exist AND, after stripping `//` line comments, must contain
a live `impl Widget for X`. Block comments out of scope (line
comments only in this directory).
CI integration:
- New "Verify Step 1b widget boundary (spec §1.4)" step in
.github/workflows/rust-check.yml right after the existing
"Verify Jian boundary invariants" step, gated to Linux runner
(matches the jian-boundaries pattern).
- Added `tools/check-jian-boundaries.sh` and
`tools/check-widget-boundary.sh` to the rust-check.yml push +
pull_request path filters so PRs editing only the checker still
trigger CI.
7-test regression matrix (positive + 6 negative cases):
- positive (real codebase) → PASS
- generic `impl<T> Widget for X` injected → FAIL F1
- direct `use openpencil_shell_core::widgets` outside host → FAIL F4
- `// glue:` tag on `impl Widget for X` line → FAIL F1 (exemption
doesn't save it; only `fn paint` lines are exempted)
- shell-core file replaced with `// stub` → FAIL R1
- grouped `use openpencil_shell_core::{widgets::TreeWidget};` → FAIL F4
- grouped `use openpencil_shell_core::{widgets};` → FAIL F4
- shell-core file body replaced with `// impl Widget for X { ... }` → FAIL R1
Codex iterate review: 5 rounds → GO. Round 1 BLOCK (greedy
WidgetHost match), R2 BLOCK + 3 CONCERN (calls/imports unchecked,
generic impls, broad exemption, filename-only count), R3 BLOCK +
CONCERN (grouped imports, commented-out impls), R4 2 NITs
(documentation parity), R5 GO clean.
2026-05-09 13:48:00 +00:00
# no business in the platform crate. (Note: `fn paint(` is allowed
# only on the `// glue:` marked line in widget_host.rs — handled by
# F3 below to keep the exemption tight.)
#
# Introduced alongside F1/F3/F4 in codex B4 R2 (the original single-
# regex check was split into per-direction blocks with named
# findings); kept stable through R3 so no in-line "fixed by …"
# citation was needed. Listed here for parity with F1/F3/F4/R1.
# ---------------------------------------------------------------------
trait_method_hits = " $( grep -RInE \
'fn[[:space:]]+(layout|access_node)\(' \
" ${ WEB_SRC } " 2>/dev/null || true ) "
if [ -n " ${ trait_method_hits } " ] ; then
2026-05-16 16:01:35 +00:00
fail_lines += ( "Forward F2: Widget trait method (layout / access_node) in op-host-web:" " ${ trait_method_hits } " )
build(step-1b): tools/check-widget-boundary.sh — Phase B4 spec §1.4 guard
Enforces the Step 1b §1.4 widget boundary invariant: widget logic
(Widget impls + layout/paint/access_node methods) lives in
crates/openpencil-shell-core/src/widgets/; shell-web's only
widget-touching file is `widget_host.rs` and even there the only
widget-method signature allowed is the `// glue:` marked paint
dispatcher.
Forward checks (no widget logic in shell-web/src/):
- F1: `impl(<...>)?[[:space:]]+(ns::)*Widget[[:space:]]+for[[:space:]]`
anywhere under shell-web/src/. Allows generic params + arbitrary
namespace depth so `impl<T> shell_core::widgets::Widget for X` is
caught. No `// glue:` exemption — Widget impls have no place in
shell-web period.
- F2: `fn[[:space:]]+(layout|access_node)\(` anywhere under
shell-web/src/. No exemption.
- F3: `fn[[:space:]]+paint\(` under shell-web/src/, EXCEPT lines in
widget_host.rs that ALSO carry `// glue:`. Tight exemption — the
marker only blesses one specific signature, not arbitrary tagged
lines.
- F4: any line under shell-web/src/ mentioning both
`openpencil_shell_core` AND `widgets`, except widget_host.rs.
Catches direct + grouped `use` forms (e.g. `use
openpencil_shell_core::{widgets::TreeWidget};`) plus path
expressions. Multi-line braced `use` is out of scope (single-line
policy in this crate).
Reverse check (shell-core/src/widgets/ has all four impls):
- R1: For each of {tree, prop_row, dropdown, text_input}, the file
must exist AND, after stripping `//` line comments, must contain
a live `impl Widget for X`. Block comments out of scope (line
comments only in this directory).
CI integration:
- New "Verify Step 1b widget boundary (spec §1.4)" step in
.github/workflows/rust-check.yml right after the existing
"Verify Jian boundary invariants" step, gated to Linux runner
(matches the jian-boundaries pattern).
- Added `tools/check-jian-boundaries.sh` and
`tools/check-widget-boundary.sh` to the rust-check.yml push +
pull_request path filters so PRs editing only the checker still
trigger CI.
7-test regression matrix (positive + 6 negative cases):
- positive (real codebase) → PASS
- generic `impl<T> Widget for X` injected → FAIL F1
- direct `use openpencil_shell_core::widgets` outside host → FAIL F4
- `// glue:` tag on `impl Widget for X` line → FAIL F1 (exemption
doesn't save it; only `fn paint` lines are exempted)
- shell-core file replaced with `// stub` → FAIL R1
- grouped `use openpencil_shell_core::{widgets::TreeWidget};` → FAIL F4
- grouped `use openpencil_shell_core::{widgets};` → FAIL F4
- shell-core file body replaced with `// impl Widget for X { ... }` → FAIL R1
Codex iterate review: 5 rounds → GO. Round 1 BLOCK (greedy
WidgetHost match), R2 BLOCK + 3 CONCERN (calls/imports unchecked,
generic impls, broad exemption, filename-only count), R3 BLOCK +
CONCERN (grouped imports, commented-out impls), R4 2 NITs
(documentation parity), R5 GO clean.
2026-05-09 13:48:00 +00:00
fi
# ---------------------------------------------------------------------
2026-05-16 16:01:35 +00:00
# Forward F3: `fn paint(` under op-host-web is allowed ONLY when the
build(step-1b): tools/check-widget-boundary.sh — Phase B4 spec §1.4 guard
Enforces the Step 1b §1.4 widget boundary invariant: widget logic
(Widget impls + layout/paint/access_node methods) lives in
crates/openpencil-shell-core/src/widgets/; shell-web's only
widget-touching file is `widget_host.rs` and even there the only
widget-method signature allowed is the `// glue:` marked paint
dispatcher.
Forward checks (no widget logic in shell-web/src/):
- F1: `impl(<...>)?[[:space:]]+(ns::)*Widget[[:space:]]+for[[:space:]]`
anywhere under shell-web/src/. Allows generic params + arbitrary
namespace depth so `impl<T> shell_core::widgets::Widget for X` is
caught. No `// glue:` exemption — Widget impls have no place in
shell-web period.
- F2: `fn[[:space:]]+(layout|access_node)\(` anywhere under
shell-web/src/. No exemption.
- F3: `fn[[:space:]]+paint\(` under shell-web/src/, EXCEPT lines in
widget_host.rs that ALSO carry `// glue:`. Tight exemption — the
marker only blesses one specific signature, not arbitrary tagged
lines.
- F4: any line under shell-web/src/ mentioning both
`openpencil_shell_core` AND `widgets`, except widget_host.rs.
Catches direct + grouped `use` forms (e.g. `use
openpencil_shell_core::{widgets::TreeWidget};`) plus path
expressions. Multi-line braced `use` is out of scope (single-line
policy in this crate).
Reverse check (shell-core/src/widgets/ has all four impls):
- R1: For each of {tree, prop_row, dropdown, text_input}, the file
must exist AND, after stripping `//` line comments, must contain
a live `impl Widget for X`. Block comments out of scope (line
comments only in this directory).
CI integration:
- New "Verify Step 1b widget boundary (spec §1.4)" step in
.github/workflows/rust-check.yml right after the existing
"Verify Jian boundary invariants" step, gated to Linux runner
(matches the jian-boundaries pattern).
- Added `tools/check-jian-boundaries.sh` and
`tools/check-widget-boundary.sh` to the rust-check.yml push +
pull_request path filters so PRs editing only the checker still
trigger CI.
7-test regression matrix (positive + 6 negative cases):
- positive (real codebase) → PASS
- generic `impl<T> Widget for X` injected → FAIL F1
- direct `use openpencil_shell_core::widgets` outside host → FAIL F4
- `// glue:` tag on `impl Widget for X` line → FAIL F1 (exemption
doesn't save it; only `fn paint` lines are exempted)
- shell-core file replaced with `// stub` → FAIL R1
- grouped `use openpencil_shell_core::{widgets::TreeWidget};` → FAIL F4
- grouped `use openpencil_shell_core::{widgets};` → FAIL F4
- shell-core file body replaced with `// impl Widget for X { ... }` → FAIL R1
Codex iterate review: 5 rounds → GO. Round 1 BLOCK (greedy
WidgetHost match), R2 BLOCK + 3 CONCERN (calls/imports unchecked,
generic impls, broad exemption, filename-only count), R3 BLOCK +
CONCERN (grouped imports, commented-out impls), R4 2 NITs
(documentation parity), R5 GO clean.
2026-05-09 13:48:00 +00:00
# line ALSO carries the `// glue:` marker AND the file is
# widget_host.rs. Tight exemption (codex B4 R2 CONCERN-2 — broad
# `// glue:` exemption could hide leaked impl/method lines if anyone
# tagged them; the F1/F2 checks above already scan everything, so
# F3's exemption only needs to bless the documented paint signature).
# ---------------------------------------------------------------------
paint_hits = " $( grep -RInE \
2026-05-11 13:30:06 +00:00
'fn[[:space:]]+paint[[:space:]]*[<(]' \
build(step-1b): tools/check-widget-boundary.sh — Phase B4 spec §1.4 guard
Enforces the Step 1b §1.4 widget boundary invariant: widget logic
(Widget impls + layout/paint/access_node methods) lives in
crates/openpencil-shell-core/src/widgets/; shell-web's only
widget-touching file is `widget_host.rs` and even there the only
widget-method signature allowed is the `// glue:` marked paint
dispatcher.
Forward checks (no widget logic in shell-web/src/):
- F1: `impl(<...>)?[[:space:]]+(ns::)*Widget[[:space:]]+for[[:space:]]`
anywhere under shell-web/src/. Allows generic params + arbitrary
namespace depth so `impl<T> shell_core::widgets::Widget for X` is
caught. No `// glue:` exemption — Widget impls have no place in
shell-web period.
- F2: `fn[[:space:]]+(layout|access_node)\(` anywhere under
shell-web/src/. No exemption.
- F3: `fn[[:space:]]+paint\(` under shell-web/src/, EXCEPT lines in
widget_host.rs that ALSO carry `// glue:`. Tight exemption — the
marker only blesses one specific signature, not arbitrary tagged
lines.
- F4: any line under shell-web/src/ mentioning both
`openpencil_shell_core` AND `widgets`, except widget_host.rs.
Catches direct + grouped `use` forms (e.g. `use
openpencil_shell_core::{widgets::TreeWidget};`) plus path
expressions. Multi-line braced `use` is out of scope (single-line
policy in this crate).
Reverse check (shell-core/src/widgets/ has all four impls):
- R1: For each of {tree, prop_row, dropdown, text_input}, the file
must exist AND, after stripping `//` line comments, must contain
a live `impl Widget for X`. Block comments out of scope (line
comments only in this directory).
CI integration:
- New "Verify Step 1b widget boundary (spec §1.4)" step in
.github/workflows/rust-check.yml right after the existing
"Verify Jian boundary invariants" step, gated to Linux runner
(matches the jian-boundaries pattern).
- Added `tools/check-jian-boundaries.sh` and
`tools/check-widget-boundary.sh` to the rust-check.yml push +
pull_request path filters so PRs editing only the checker still
trigger CI.
7-test regression matrix (positive + 6 negative cases):
- positive (real codebase) → PASS
- generic `impl<T> Widget for X` injected → FAIL F1
- direct `use openpencil_shell_core::widgets` outside host → FAIL F4
- `// glue:` tag on `impl Widget for X` line → FAIL F1 (exemption
doesn't save it; only `fn paint` lines are exempted)
- shell-core file replaced with `// stub` → FAIL R1
- grouped `use openpencil_shell_core::{widgets::TreeWidget};` → FAIL F4
- grouped `use openpencil_shell_core::{widgets};` → FAIL F4
- shell-core file body replaced with `// impl Widget for X { ... }` → FAIL R1
Codex iterate review: 5 rounds → GO. Round 1 BLOCK (greedy
WidgetHost match), R2 BLOCK + 3 CONCERN (calls/imports unchecked,
generic impls, broad exemption, filename-only count), R3 BLOCK +
CONCERN (grouped imports, commented-out impls), R4 2 NITs
(documentation parity), R5 GO clean.
2026-05-09 13:48:00 +00:00
" ${ WEB_SRC } " 2>/dev/null || true ) "
if [ -n " ${ paint_hits } " ] ; then
2026-05-11 13:30:06 +00:00
# Allow `fn paint(` in `widget_host.rs` or any sibling under
# `widget_host/`, provided the marker `// glue:` is on the
# IMMEDIATELY PRECEDING line. This ties the marker to a
# specific function (codex CONCERN: a file-level marker was
# too permissive — a developer could add `// glue:` once
# anywhere and then sneak in arbitrary extra `fn paint`
# helpers). The marker on the line above is rustfmt-stable:
# fmt never reorders comment-then-fn pairs.
#
# Implementation: a single `awk` pass over the allowed files,
# plus a separate grep for `fn paint(` in non-allowed files
# (those are unconditional violations). No while loop / no
# here-string — codex CONCERN about `set -euo pipefail`
# interactions.
#
# `glue_violations` — `fn paint(` lines inside the widget_host
# module whose preceding line is NOT `// glue:`.
glue_violations = " $( awk '
/^[ [ :space:] ] *\/ \/ [ [ :space:] ] *glue:[ [ :space:] ] *$/ {
prev_was_glue = 1; next
}
/fn[ [ :space:] ] +paint[ [ :space:] ] *[ <( ] / {
if ( !prev_was_glue) {
printf "%s:%d:%s\n" , FILENAME, FNR, $0
}
prev_was_glue = 0
next
}
{ prev_was_glue = 0 }
' " ${ WEB_SRC } /widget_host.rs " " ${ WEB_SRC } /widget_host " /*.rs 2>/dev/null || true ) "
2026-05-16 16:01:35 +00:00
# `outside_module_files` — files in op-host-web/src/ that
2026-05-11 13:30:06 +00:00
# define `fn paint(` but are NOT in the widget_host module
# at all. These violate F3 unconditionally.
outside_module_files = " $( grep -RIlE \
'fn[[:space:]]+paint[[:space:]]*[<(]' \
" ${ WEB_SRC } " 2>/dev/null \
2026-05-16 16:01:35 +00:00
| grep -vE '^crates/op-host-web/src/widget_host(\.rs|/[^/]+\.rs)$' \
2026-05-11 13:30:06 +00:00
| LC_ALL = C sort -u || true ) "
outside_module_hits = ""
if [ -n " ${ outside_module_files } " ] ; then
outside_module_hits = " $( grep -nE \
'fn[[:space:]]+paint[[:space:]]*[<(]' \
${ outside_module_files } 2>/dev/null || true ) "
fi
combined_paint = ""
[ -n " ${ glue_violations } " ] && combined_paint = " ${ glue_violations } "
if [ -n " ${ outside_module_hits } " ] ; then
if [ -n " ${ combined_paint } " ] ; then
combined_paint = " ${ combined_paint } " $'\n' " ${ outside_module_hits } "
else
combined_paint = " ${ outside_module_hits } "
fi
fi
if [ -n " ${ combined_paint } " ] ; then
2026-05-16 16:01:35 +00:00
fail_lines += ( "Forward F3: fn paint( in op-host-web without a '// glue:' marker on the immediately preceding line, OR outside the widget_host module (allowed locations: widget_host.rs and widget_host/* sibling files):" " ${ combined_paint } " )
build(step-1b): tools/check-widget-boundary.sh — Phase B4 spec §1.4 guard
Enforces the Step 1b §1.4 widget boundary invariant: widget logic
(Widget impls + layout/paint/access_node methods) lives in
crates/openpencil-shell-core/src/widgets/; shell-web's only
widget-touching file is `widget_host.rs` and even there the only
widget-method signature allowed is the `// glue:` marked paint
dispatcher.
Forward checks (no widget logic in shell-web/src/):
- F1: `impl(<...>)?[[:space:]]+(ns::)*Widget[[:space:]]+for[[:space:]]`
anywhere under shell-web/src/. Allows generic params + arbitrary
namespace depth so `impl<T> shell_core::widgets::Widget for X` is
caught. No `// glue:` exemption — Widget impls have no place in
shell-web period.
- F2: `fn[[:space:]]+(layout|access_node)\(` anywhere under
shell-web/src/. No exemption.
- F3: `fn[[:space:]]+paint\(` under shell-web/src/, EXCEPT lines in
widget_host.rs that ALSO carry `// glue:`. Tight exemption — the
marker only blesses one specific signature, not arbitrary tagged
lines.
- F4: any line under shell-web/src/ mentioning both
`openpencil_shell_core` AND `widgets`, except widget_host.rs.
Catches direct + grouped `use` forms (e.g. `use
openpencil_shell_core::{widgets::TreeWidget};`) plus path
expressions. Multi-line braced `use` is out of scope (single-line
policy in this crate).
Reverse check (shell-core/src/widgets/ has all four impls):
- R1: For each of {tree, prop_row, dropdown, text_input}, the file
must exist AND, after stripping `//` line comments, must contain
a live `impl Widget for X`. Block comments out of scope (line
comments only in this directory).
CI integration:
- New "Verify Step 1b widget boundary (spec §1.4)" step in
.github/workflows/rust-check.yml right after the existing
"Verify Jian boundary invariants" step, gated to Linux runner
(matches the jian-boundaries pattern).
- Added `tools/check-jian-boundaries.sh` and
`tools/check-widget-boundary.sh` to the rust-check.yml push +
pull_request path filters so PRs editing only the checker still
trigger CI.
7-test regression matrix (positive + 6 negative cases):
- positive (real codebase) → PASS
- generic `impl<T> Widget for X` injected → FAIL F1
- direct `use openpencil_shell_core::widgets` outside host → FAIL F4
- `// glue:` tag on `impl Widget for X` line → FAIL F1 (exemption
doesn't save it; only `fn paint` lines are exempted)
- shell-core file replaced with `// stub` → FAIL R1
- grouped `use openpencil_shell_core::{widgets::TreeWidget};` → FAIL F4
- grouped `use openpencil_shell_core::{widgets};` → FAIL F4
- shell-core file body replaced with `// impl Widget for X { ... }` → FAIL R1
Codex iterate review: 5 rounds → GO. Round 1 BLOCK (greedy
WidgetHost match), R2 BLOCK + 3 CONCERN (calls/imports unchecked,
generic impls, broad exemption, filename-only count), R3 BLOCK +
CONCERN (grouped imports, commented-out impls), R4 2 NITs
(documentation parity), R5 GO clean.
2026-05-09 13:48:00 +00:00
fi
fi
# ---------------------------------------------------------------------
2026-05-16 16:01:35 +00:00
# Forward F4: only widget_host.rs may import `op_editor_ui::
build(step-1b): tools/check-widget-boundary.sh — Phase B4 spec §1.4 guard
Enforces the Step 1b §1.4 widget boundary invariant: widget logic
(Widget impls + layout/paint/access_node methods) lives in
crates/openpencil-shell-core/src/widgets/; shell-web's only
widget-touching file is `widget_host.rs` and even there the only
widget-method signature allowed is the `// glue:` marked paint
dispatcher.
Forward checks (no widget logic in shell-web/src/):
- F1: `impl(<...>)?[[:space:]]+(ns::)*Widget[[:space:]]+for[[:space:]]`
anywhere under shell-web/src/. Allows generic params + arbitrary
namespace depth so `impl<T> shell_core::widgets::Widget for X` is
caught. No `// glue:` exemption — Widget impls have no place in
shell-web period.
- F2: `fn[[:space:]]+(layout|access_node)\(` anywhere under
shell-web/src/. No exemption.
- F3: `fn[[:space:]]+paint\(` under shell-web/src/, EXCEPT lines in
widget_host.rs that ALSO carry `// glue:`. Tight exemption — the
marker only blesses one specific signature, not arbitrary tagged
lines.
- F4: any line under shell-web/src/ mentioning both
`openpencil_shell_core` AND `widgets`, except widget_host.rs.
Catches direct + grouped `use` forms (e.g. `use
openpencil_shell_core::{widgets::TreeWidget};`) plus path
expressions. Multi-line braced `use` is out of scope (single-line
policy in this crate).
Reverse check (shell-core/src/widgets/ has all four impls):
- R1: For each of {tree, prop_row, dropdown, text_input}, the file
must exist AND, after stripping `//` line comments, must contain
a live `impl Widget for X`. Block comments out of scope (line
comments only in this directory).
CI integration:
- New "Verify Step 1b widget boundary (spec §1.4)" step in
.github/workflows/rust-check.yml right after the existing
"Verify Jian boundary invariants" step, gated to Linux runner
(matches the jian-boundaries pattern).
- Added `tools/check-jian-boundaries.sh` and
`tools/check-widget-boundary.sh` to the rust-check.yml push +
pull_request path filters so PRs editing only the checker still
trigger CI.
7-test regression matrix (positive + 6 negative cases):
- positive (real codebase) → PASS
- generic `impl<T> Widget for X` injected → FAIL F1
- direct `use openpencil_shell_core::widgets` outside host → FAIL F4
- `// glue:` tag on `impl Widget for X` line → FAIL F1 (exemption
doesn't save it; only `fn paint` lines are exempted)
- shell-core file replaced with `// stub` → FAIL R1
- grouped `use openpencil_shell_core::{widgets::TreeWidget};` → FAIL F4
- grouped `use openpencil_shell_core::{widgets};` → FAIL F4
- shell-core file body replaced with `// impl Widget for X { ... }` → FAIL R1
Codex iterate review: 5 rounds → GO. Round 1 BLOCK (greedy
WidgetHost match), R2 BLOCK + 3 CONCERN (calls/imports unchecked,
generic impls, broad exemption, filename-only count), R3 BLOCK +
CONCERN (grouped imports, commented-out impls), R4 2 NITs
(documentation parity), R5 GO clean.
2026-05-09 13:48:00 +00:00
# widgets` in any form. Catches:
2026-05-16 16:01:35 +00:00
# - `use op_editor_ui::widgets;` (direct)
# - `use op_editor_ui::widgets::Foo;` (direct)
# - `use op_editor_ui::{widgets};` (grouped)
# - `use op_editor_ui::{widgets::Foo};` (grouped)
# - `op_editor_ui::widgets::call()` (path expr)
build(step-1b): tools/check-widget-boundary.sh — Phase B4 spec §1.4 guard
Enforces the Step 1b §1.4 widget boundary invariant: widget logic
(Widget impls + layout/paint/access_node methods) lives in
crates/openpencil-shell-core/src/widgets/; shell-web's only
widget-touching file is `widget_host.rs` and even there the only
widget-method signature allowed is the `// glue:` marked paint
dispatcher.
Forward checks (no widget logic in shell-web/src/):
- F1: `impl(<...>)?[[:space:]]+(ns::)*Widget[[:space:]]+for[[:space:]]`
anywhere under shell-web/src/. Allows generic params + arbitrary
namespace depth so `impl<T> shell_core::widgets::Widget for X` is
caught. No `// glue:` exemption — Widget impls have no place in
shell-web period.
- F2: `fn[[:space:]]+(layout|access_node)\(` anywhere under
shell-web/src/. No exemption.
- F3: `fn[[:space:]]+paint\(` under shell-web/src/, EXCEPT lines in
widget_host.rs that ALSO carry `// glue:`. Tight exemption — the
marker only blesses one specific signature, not arbitrary tagged
lines.
- F4: any line under shell-web/src/ mentioning both
`openpencil_shell_core` AND `widgets`, except widget_host.rs.
Catches direct + grouped `use` forms (e.g. `use
openpencil_shell_core::{widgets::TreeWidget};`) plus path
expressions. Multi-line braced `use` is out of scope (single-line
policy in this crate).
Reverse check (shell-core/src/widgets/ has all four impls):
- R1: For each of {tree, prop_row, dropdown, text_input}, the file
must exist AND, after stripping `//` line comments, must contain
a live `impl Widget for X`. Block comments out of scope (line
comments only in this directory).
CI integration:
- New "Verify Step 1b widget boundary (spec §1.4)" step in
.github/workflows/rust-check.yml right after the existing
"Verify Jian boundary invariants" step, gated to Linux runner
(matches the jian-boundaries pattern).
- Added `tools/check-jian-boundaries.sh` and
`tools/check-widget-boundary.sh` to the rust-check.yml push +
pull_request path filters so PRs editing only the checker still
trigger CI.
7-test regression matrix (positive + 6 negative cases):
- positive (real codebase) → PASS
- generic `impl<T> Widget for X` injected → FAIL F1
- direct `use openpencil_shell_core::widgets` outside host → FAIL F4
- `// glue:` tag on `impl Widget for X` line → FAIL F1 (exemption
doesn't save it; only `fn paint` lines are exempted)
- shell-core file replaced with `// stub` → FAIL R1
- grouped `use openpencil_shell_core::{widgets::TreeWidget};` → FAIL F4
- grouped `use openpencil_shell_core::{widgets};` → FAIL F4
- shell-core file body replaced with `// impl Widget for X { ... }` → FAIL R1
Codex iterate review: 5 rounds → GO. Round 1 BLOCK (greedy
WidgetHost match), R2 BLOCK + 3 CONCERN (calls/imports unchecked,
generic impls, broad exemption, filename-only count), R3 BLOCK +
CONCERN (grouped imports, commented-out impls), R4 2 NITs
(documentation parity), R5 GO clean.
2026-05-09 13:48:00 +00:00
# - Multi-line grouped imports are NOT caught (single-line
# scope only — code review enforces single-line use stmts in this
# crate; the cost-benefit doesn't justify a multi-line parser).
#
# Codex B4 R2 BLOCK-fix introduced F4; R3 BLOCK refined for grouped
# import forms — the previous regex required a literal `::widgets`
2026-05-16 16:01:35 +00:00
# right after `op_editor_ui`, missing the brace form.
# Solution: any line mentioning `op_editor_ui` AND `widgets`
build(step-1b): tools/check-widget-boundary.sh — Phase B4 spec §1.4 guard
Enforces the Step 1b §1.4 widget boundary invariant: widget logic
(Widget impls + layout/paint/access_node methods) lives in
crates/openpencil-shell-core/src/widgets/; shell-web's only
widget-touching file is `widget_host.rs` and even there the only
widget-method signature allowed is the `// glue:` marked paint
dispatcher.
Forward checks (no widget logic in shell-web/src/):
- F1: `impl(<...>)?[[:space:]]+(ns::)*Widget[[:space:]]+for[[:space:]]`
anywhere under shell-web/src/. Allows generic params + arbitrary
namespace depth so `impl<T> shell_core::widgets::Widget for X` is
caught. No `// glue:` exemption — Widget impls have no place in
shell-web period.
- F2: `fn[[:space:]]+(layout|access_node)\(` anywhere under
shell-web/src/. No exemption.
- F3: `fn[[:space:]]+paint\(` under shell-web/src/, EXCEPT lines in
widget_host.rs that ALSO carry `// glue:`. Tight exemption — the
marker only blesses one specific signature, not arbitrary tagged
lines.
- F4: any line under shell-web/src/ mentioning both
`openpencil_shell_core` AND `widgets`, except widget_host.rs.
Catches direct + grouped `use` forms (e.g. `use
openpencil_shell_core::{widgets::TreeWidget};`) plus path
expressions. Multi-line braced `use` is out of scope (single-line
policy in this crate).
Reverse check (shell-core/src/widgets/ has all four impls):
- R1: For each of {tree, prop_row, dropdown, text_input}, the file
must exist AND, after stripping `//` line comments, must contain
a live `impl Widget for X`. Block comments out of scope (line
comments only in this directory).
CI integration:
- New "Verify Step 1b widget boundary (spec §1.4)" step in
.github/workflows/rust-check.yml right after the existing
"Verify Jian boundary invariants" step, gated to Linux runner
(matches the jian-boundaries pattern).
- Added `tools/check-jian-boundaries.sh` and
`tools/check-widget-boundary.sh` to the rust-check.yml push +
pull_request path filters so PRs editing only the checker still
trigger CI.
7-test regression matrix (positive + 6 negative cases):
- positive (real codebase) → PASS
- generic `impl<T> Widget for X` injected → FAIL F1
- direct `use openpencil_shell_core::widgets` outside host → FAIL F4
- `// glue:` tag on `impl Widget for X` line → FAIL F1 (exemption
doesn't save it; only `fn paint` lines are exempted)
- shell-core file replaced with `// stub` → FAIL R1
- grouped `use openpencil_shell_core::{widgets::TreeWidget};` → FAIL F4
- grouped `use openpencil_shell_core::{widgets};` → FAIL F4
- shell-core file body replaced with `// impl Widget for X { ... }` → FAIL R1
Codex iterate review: 5 rounds → GO. Round 1 BLOCK (greedy
WidgetHost match), R2 BLOCK + 3 CONCERN (calls/imports unchecked,
generic impls, broad exemption, filename-only count), R3 BLOCK +
CONCERN (grouped imports, commented-out impls), R4 2 NITs
(documentation parity), R5 GO clean.
2026-05-09 13:48:00 +00:00
# is a hit (single grep pass picks both patterns up).
# ---------------------------------------------------------------------
2026-05-16 16:01:35 +00:00
core_ref_hits = " $( grep -RIn 'op_editor_ui' " ${ WEB_SRC } " 2>/dev/null || true ) "
build(step-1b): tools/check-widget-boundary.sh — Phase B4 spec §1.4 guard
Enforces the Step 1b §1.4 widget boundary invariant: widget logic
(Widget impls + layout/paint/access_node methods) lives in
crates/openpencil-shell-core/src/widgets/; shell-web's only
widget-touching file is `widget_host.rs` and even there the only
widget-method signature allowed is the `// glue:` marked paint
dispatcher.
Forward checks (no widget logic in shell-web/src/):
- F1: `impl(<...>)?[[:space:]]+(ns::)*Widget[[:space:]]+for[[:space:]]`
anywhere under shell-web/src/. Allows generic params + arbitrary
namespace depth so `impl<T> shell_core::widgets::Widget for X` is
caught. No `// glue:` exemption — Widget impls have no place in
shell-web period.
- F2: `fn[[:space:]]+(layout|access_node)\(` anywhere under
shell-web/src/. No exemption.
- F3: `fn[[:space:]]+paint\(` under shell-web/src/, EXCEPT lines in
widget_host.rs that ALSO carry `// glue:`. Tight exemption — the
marker only blesses one specific signature, not arbitrary tagged
lines.
- F4: any line under shell-web/src/ mentioning both
`openpencil_shell_core` AND `widgets`, except widget_host.rs.
Catches direct + grouped `use` forms (e.g. `use
openpencil_shell_core::{widgets::TreeWidget};`) plus path
expressions. Multi-line braced `use` is out of scope (single-line
policy in this crate).
Reverse check (shell-core/src/widgets/ has all four impls):
- R1: For each of {tree, prop_row, dropdown, text_input}, the file
must exist AND, after stripping `//` line comments, must contain
a live `impl Widget for X`. Block comments out of scope (line
comments only in this directory).
CI integration:
- New "Verify Step 1b widget boundary (spec §1.4)" step in
.github/workflows/rust-check.yml right after the existing
"Verify Jian boundary invariants" step, gated to Linux runner
(matches the jian-boundaries pattern).
- Added `tools/check-jian-boundaries.sh` and
`tools/check-widget-boundary.sh` to the rust-check.yml push +
pull_request path filters so PRs editing only the checker still
trigger CI.
7-test regression matrix (positive + 6 negative cases):
- positive (real codebase) → PASS
- generic `impl<T> Widget for X` injected → FAIL F1
- direct `use openpencil_shell_core::widgets` outside host → FAIL F4
- `// glue:` tag on `impl Widget for X` line → FAIL F1 (exemption
doesn't save it; only `fn paint` lines are exempted)
- shell-core file replaced with `// stub` → FAIL R1
- grouped `use openpencil_shell_core::{widgets::TreeWidget};` → FAIL F4
- grouped `use openpencil_shell_core::{widgets};` → FAIL F4
- shell-core file body replaced with `// impl Widget for X { ... }` → FAIL R1
Codex iterate review: 5 rounds → GO. Round 1 BLOCK (greedy
WidgetHost match), R2 BLOCK + 3 CONCERN (calls/imports unchecked,
generic impls, broad exemption, filename-only count), R3 BLOCK +
CONCERN (grouped imports, commented-out impls), R4 2 NITs
(documentation parity), R5 GO clean.
2026-05-09 13:48:00 +00:00
if [ -n " ${ core_ref_hits } " ] ; then
# Filter to lines that ALSO mention `widgets`, then drop the
# widget_host.rs allowance.
2026-05-11 13:30:06 +00:00
# widget_host module is allowed to span sibling files under
# `widget_host/` (spec amendment 2026-05-11).
build(step-1b): tools/check-widget-boundary.sh — Phase B4 spec §1.4 guard
Enforces the Step 1b §1.4 widget boundary invariant: widget logic
(Widget impls + layout/paint/access_node methods) lives in
crates/openpencil-shell-core/src/widgets/; shell-web's only
widget-touching file is `widget_host.rs` and even there the only
widget-method signature allowed is the `// glue:` marked paint
dispatcher.
Forward checks (no widget logic in shell-web/src/):
- F1: `impl(<...>)?[[:space:]]+(ns::)*Widget[[:space:]]+for[[:space:]]`
anywhere under shell-web/src/. Allows generic params + arbitrary
namespace depth so `impl<T> shell_core::widgets::Widget for X` is
caught. No `// glue:` exemption — Widget impls have no place in
shell-web period.
- F2: `fn[[:space:]]+(layout|access_node)\(` anywhere under
shell-web/src/. No exemption.
- F3: `fn[[:space:]]+paint\(` under shell-web/src/, EXCEPT lines in
widget_host.rs that ALSO carry `// glue:`. Tight exemption — the
marker only blesses one specific signature, not arbitrary tagged
lines.
- F4: any line under shell-web/src/ mentioning both
`openpencil_shell_core` AND `widgets`, except widget_host.rs.
Catches direct + grouped `use` forms (e.g. `use
openpencil_shell_core::{widgets::TreeWidget};`) plus path
expressions. Multi-line braced `use` is out of scope (single-line
policy in this crate).
Reverse check (shell-core/src/widgets/ has all four impls):
- R1: For each of {tree, prop_row, dropdown, text_input}, the file
must exist AND, after stripping `//` line comments, must contain
a live `impl Widget for X`. Block comments out of scope (line
comments only in this directory).
CI integration:
- New "Verify Step 1b widget boundary (spec §1.4)" step in
.github/workflows/rust-check.yml right after the existing
"Verify Jian boundary invariants" step, gated to Linux runner
(matches the jian-boundaries pattern).
- Added `tools/check-jian-boundaries.sh` and
`tools/check-widget-boundary.sh` to the rust-check.yml push +
pull_request path filters so PRs editing only the checker still
trigger CI.
7-test regression matrix (positive + 6 negative cases):
- positive (real codebase) → PASS
- generic `impl<T> Widget for X` injected → FAIL F1
- direct `use openpencil_shell_core::widgets` outside host → FAIL F4
- `// glue:` tag on `impl Widget for X` line → FAIL F1 (exemption
doesn't save it; only `fn paint` lines are exempted)
- shell-core file replaced with `// stub` → FAIL R1
- grouped `use openpencil_shell_core::{widgets::TreeWidget};` → FAIL F4
- grouped `use openpencil_shell_core::{widgets};` → FAIL F4
- shell-core file body replaced with `// impl Widget for X { ... }` → FAIL R1
Codex iterate review: 5 rounds → GO. Round 1 BLOCK (greedy
WidgetHost match), R2 BLOCK + 3 CONCERN (calls/imports unchecked,
generic impls, broad exemption, filename-only count), R3 BLOCK +
CONCERN (grouped imports, commented-out impls), R4 2 NITs
(documentation parity), R5 GO clean.
2026-05-09 13:48:00 +00:00
illegal_imports = " $( printf '%s\n' " ${ core_ref_hits } " \
| grep 'widgets' \
2026-05-16 16:01:35 +00:00
| grep -vE '^crates/op-host-web/src/widget_host(\.rs|/[^/]+\.rs):' \
build(step-1b): tools/check-widget-boundary.sh — Phase B4 spec §1.4 guard
Enforces the Step 1b §1.4 widget boundary invariant: widget logic
(Widget impls + layout/paint/access_node methods) lives in
crates/openpencil-shell-core/src/widgets/; shell-web's only
widget-touching file is `widget_host.rs` and even there the only
widget-method signature allowed is the `// glue:` marked paint
dispatcher.
Forward checks (no widget logic in shell-web/src/):
- F1: `impl(<...>)?[[:space:]]+(ns::)*Widget[[:space:]]+for[[:space:]]`
anywhere under shell-web/src/. Allows generic params + arbitrary
namespace depth so `impl<T> shell_core::widgets::Widget for X` is
caught. No `// glue:` exemption — Widget impls have no place in
shell-web period.
- F2: `fn[[:space:]]+(layout|access_node)\(` anywhere under
shell-web/src/. No exemption.
- F3: `fn[[:space:]]+paint\(` under shell-web/src/, EXCEPT lines in
widget_host.rs that ALSO carry `// glue:`. Tight exemption — the
marker only blesses one specific signature, not arbitrary tagged
lines.
- F4: any line under shell-web/src/ mentioning both
`openpencil_shell_core` AND `widgets`, except widget_host.rs.
Catches direct + grouped `use` forms (e.g. `use
openpencil_shell_core::{widgets::TreeWidget};`) plus path
expressions. Multi-line braced `use` is out of scope (single-line
policy in this crate).
Reverse check (shell-core/src/widgets/ has all four impls):
- R1: For each of {tree, prop_row, dropdown, text_input}, the file
must exist AND, after stripping `//` line comments, must contain
a live `impl Widget for X`. Block comments out of scope (line
comments only in this directory).
CI integration:
- New "Verify Step 1b widget boundary (spec §1.4)" step in
.github/workflows/rust-check.yml right after the existing
"Verify Jian boundary invariants" step, gated to Linux runner
(matches the jian-boundaries pattern).
- Added `tools/check-jian-boundaries.sh` and
`tools/check-widget-boundary.sh` to the rust-check.yml push +
pull_request path filters so PRs editing only the checker still
trigger CI.
7-test regression matrix (positive + 6 negative cases):
- positive (real codebase) → PASS
- generic `impl<T> Widget for X` injected → FAIL F1
- direct `use openpencil_shell_core::widgets` outside host → FAIL F4
- `// glue:` tag on `impl Widget for X` line → FAIL F1 (exemption
doesn't save it; only `fn paint` lines are exempted)
- shell-core file replaced with `// stub` → FAIL R1
- grouped `use openpencil_shell_core::{widgets::TreeWidget};` → FAIL F4
- grouped `use openpencil_shell_core::{widgets};` → FAIL F4
- shell-core file body replaced with `// impl Widget for X { ... }` → FAIL R1
Codex iterate review: 5 rounds → GO. Round 1 BLOCK (greedy
WidgetHost match), R2 BLOCK + 3 CONCERN (calls/imports unchecked,
generic impls, broad exemption, filename-only count), R3 BLOCK +
CONCERN (grouped imports, commented-out impls), R4 2 NITs
(documentation parity), R5 GO clean.
2026-05-09 13:48:00 +00:00
|| true ) "
if [ -n " ${ illegal_imports } " ] ; then
2026-05-16 16:01:35 +00:00
fail_lines += ( "Forward F4: op_editor_ui::widgets reference outside widget_host.rs (covers direct + grouped use forms):" " ${ illegal_imports } " )
build(step-1b): tools/check-widget-boundary.sh — Phase B4 spec §1.4 guard
Enforces the Step 1b §1.4 widget boundary invariant: widget logic
(Widget impls + layout/paint/access_node methods) lives in
crates/openpencil-shell-core/src/widgets/; shell-web's only
widget-touching file is `widget_host.rs` and even there the only
widget-method signature allowed is the `// glue:` marked paint
dispatcher.
Forward checks (no widget logic in shell-web/src/):
- F1: `impl(<...>)?[[:space:]]+(ns::)*Widget[[:space:]]+for[[:space:]]`
anywhere under shell-web/src/. Allows generic params + arbitrary
namespace depth so `impl<T> shell_core::widgets::Widget for X` is
caught. No `// glue:` exemption — Widget impls have no place in
shell-web period.
- F2: `fn[[:space:]]+(layout|access_node)\(` anywhere under
shell-web/src/. No exemption.
- F3: `fn[[:space:]]+paint\(` under shell-web/src/, EXCEPT lines in
widget_host.rs that ALSO carry `// glue:`. Tight exemption — the
marker only blesses one specific signature, not arbitrary tagged
lines.
- F4: any line under shell-web/src/ mentioning both
`openpencil_shell_core` AND `widgets`, except widget_host.rs.
Catches direct + grouped `use` forms (e.g. `use
openpencil_shell_core::{widgets::TreeWidget};`) plus path
expressions. Multi-line braced `use` is out of scope (single-line
policy in this crate).
Reverse check (shell-core/src/widgets/ has all four impls):
- R1: For each of {tree, prop_row, dropdown, text_input}, the file
must exist AND, after stripping `//` line comments, must contain
a live `impl Widget for X`. Block comments out of scope (line
comments only in this directory).
CI integration:
- New "Verify Step 1b widget boundary (spec §1.4)" step in
.github/workflows/rust-check.yml right after the existing
"Verify Jian boundary invariants" step, gated to Linux runner
(matches the jian-boundaries pattern).
- Added `tools/check-jian-boundaries.sh` and
`tools/check-widget-boundary.sh` to the rust-check.yml push +
pull_request path filters so PRs editing only the checker still
trigger CI.
7-test regression matrix (positive + 6 negative cases):
- positive (real codebase) → PASS
- generic `impl<T> Widget for X` injected → FAIL F1
- direct `use openpencil_shell_core::widgets` outside host → FAIL F4
- `// glue:` tag on `impl Widget for X` line → FAIL F1 (exemption
doesn't save it; only `fn paint` lines are exempted)
- shell-core file replaced with `// stub` → FAIL R1
- grouped `use openpencil_shell_core::{widgets::TreeWidget};` → FAIL F4
- grouped `use openpencil_shell_core::{widgets};` → FAIL F4
- shell-core file body replaced with `// impl Widget for X { ... }` → FAIL R1
Codex iterate review: 5 rounds → GO. Round 1 BLOCK (greedy
WidgetHost match), R2 BLOCK + 3 CONCERN (calls/imports unchecked,
generic impls, broad exemption, filename-only count), R3 BLOCK +
CONCERN (grouped imports, commented-out impls), R4 2 NITs
(documentation parity), R5 GO clean.
2026-05-09 13:48:00 +00:00
fi
fi
# ---------------------------------------------------------------------
# Reverse R1: each of the four expected widget impl files exists AND
# carries a real `impl Widget for X` line that is NOT a Rust
# line-comment.
#
# Codex B4 R2 CONCERN-3 introduced R1; R3 CONCERN refined the impl
# grep so a commented-out `// impl Widget for TextInput` stub does
# not silently satisfy the check. The stripping uses sed to drop
# `//`-prefixed content (after optional leading whitespace) before
# grepping; block comments (`/* … */`) are not handled because the
2026-05-16 16:01:35 +00:00
# Rust style in op-editor-ui/src/widgets/ uses line comments only.
build(step-1b): tools/check-widget-boundary.sh — Phase B4 spec §1.4 guard
Enforces the Step 1b §1.4 widget boundary invariant: widget logic
(Widget impls + layout/paint/access_node methods) lives in
crates/openpencil-shell-core/src/widgets/; shell-web's only
widget-touching file is `widget_host.rs` and even there the only
widget-method signature allowed is the `// glue:` marked paint
dispatcher.
Forward checks (no widget logic in shell-web/src/):
- F1: `impl(<...>)?[[:space:]]+(ns::)*Widget[[:space:]]+for[[:space:]]`
anywhere under shell-web/src/. Allows generic params + arbitrary
namespace depth so `impl<T> shell_core::widgets::Widget for X` is
caught. No `// glue:` exemption — Widget impls have no place in
shell-web period.
- F2: `fn[[:space:]]+(layout|access_node)\(` anywhere under
shell-web/src/. No exemption.
- F3: `fn[[:space:]]+paint\(` under shell-web/src/, EXCEPT lines in
widget_host.rs that ALSO carry `// glue:`. Tight exemption — the
marker only blesses one specific signature, not arbitrary tagged
lines.
- F4: any line under shell-web/src/ mentioning both
`openpencil_shell_core` AND `widgets`, except widget_host.rs.
Catches direct + grouped `use` forms (e.g. `use
openpencil_shell_core::{widgets::TreeWidget};`) plus path
expressions. Multi-line braced `use` is out of scope (single-line
policy in this crate).
Reverse check (shell-core/src/widgets/ has all four impls):
- R1: For each of {tree, prop_row, dropdown, text_input}, the file
must exist AND, after stripping `//` line comments, must contain
a live `impl Widget for X`. Block comments out of scope (line
comments only in this directory).
CI integration:
- New "Verify Step 1b widget boundary (spec §1.4)" step in
.github/workflows/rust-check.yml right after the existing
"Verify Jian boundary invariants" step, gated to Linux runner
(matches the jian-boundaries pattern).
- Added `tools/check-jian-boundaries.sh` and
`tools/check-widget-boundary.sh` to the rust-check.yml push +
pull_request path filters so PRs editing only the checker still
trigger CI.
7-test regression matrix (positive + 6 negative cases):
- positive (real codebase) → PASS
- generic `impl<T> Widget for X` injected → FAIL F1
- direct `use openpencil_shell_core::widgets` outside host → FAIL F4
- `// glue:` tag on `impl Widget for X` line → FAIL F1 (exemption
doesn't save it; only `fn paint` lines are exempted)
- shell-core file replaced with `// stub` → FAIL R1
- grouped `use openpencil_shell_core::{widgets::TreeWidget};` → FAIL F4
- grouped `use openpencil_shell_core::{widgets};` → FAIL F4
- shell-core file body replaced with `// impl Widget for X { ... }` → FAIL R1
Codex iterate review: 5 rounds → GO. Round 1 BLOCK (greedy
WidgetHost match), R2 BLOCK + 3 CONCERN (calls/imports unchecked,
generic impls, broad exemption, filename-only count), R3 BLOCK +
CONCERN (grouped imports, commented-out impls), R4 2 NITs
(documentation parity), R5 GO clean.
2026-05-09 13:48:00 +00:00
# ---------------------------------------------------------------------
required_widgets = ( "tree" "prop_row" "dropdown" "text_input" )
for w in " ${ required_widgets [@] } " ; do
file = " ${ CORE_WIDGETS } / ${ w } .rs "
if [ ! -f " ${ file } " ] ; then
fail_lines += ( " Reverse R1: ${ file } missing " )
continue
fi
uncommented = " $( sed -E 's@[[:space:]]*//.*@@' " ${ file } " ) "
if ! printf '%s\n' " ${ uncommented } " \
| grep -qE 'impl(<[^>]+>)?[[:space:]]+([[:alnum:]_]+::)*Widget[[:space:]]+for[[:space:]]' ; then
fail_lines += ( " Reverse R1: ${ file } has no live \`impl Widget for X\` (file exists but stale, or impl is commented out) " )
fi
done
# ---------------------------------------------------------------------
# Report.
# ---------------------------------------------------------------------
if [ " ${# fail_lines [@] } " -gt 0 ] ; then
printf 'FAIL: widget boundary check\n' >& 2
printf '\n' >& 2
for line in " ${ fail_lines [@] } " ; do
printf '%s\n' " ${ line } " >& 2
done
exit 1
fi
echo "PASS: widget boundary check"