Skip to content

refactor(gateway): split monolithic style.css and app.js into per-surface modules#2683

Merged
ilblackdragon merged 1 commit intostagingfrom
refactor/gateway-static-split
Apr 19, 2026
Merged

refactor(gateway): split monolithic style.css and app.js into per-surface modules#2683
ilblackdragon merged 1 commit intostagingfrom
refactor/gateway-static-split

Conversation

@ilblackdragon
Copy link
Copy Markdown
Member

Summary

  • Splits the gateway's two largest files — static/style.css (6 887 lines) and static/app.js (11 189 lines) — into per-surface / per-concern modules so PRs touching different tabs stop colliding on the same file.
  • STYLE_CSS and APP_JS in crates/ironclaw_gateway/src/assets.rs now concat!(include_str!(...)) the split pieces at compile time. /style.css and /app.js URLs are unchanged, the workspace custom.css overlay still works, and bytes served are the same (plus a \n separator between files).
  • Admin assets move under static/admin/ for symmetry; docs + pre-commit-safety + CI workflow updated to point at the new layout and validate split files per-file.

Layout

  • CSS — 20 files under static/styles/{base,layout}.css + styles/{components,primitives,surfaces}/*.css
  • JS — 24 files under static/js/{core,surfaces}/*.js
  • Adminstatic/admin/{admin.css, admin.js}

Cuts land on function / top-level-symbol boundaries. A diff <(sort original) <(cat splits | sort) returned zero; node --check validates the concat.

Non-goals

No URL, CSP, behaviour, or logic change. This is a pure file-layout refactor — the follow-up design-system work (token restructure, primitives, Defuse-inspired visual language) lands on top of this clean split in separate PRs.

Test plan

  • cargo check -p ironclaw_gateway — clean
  • cargo clippy --all --benches --tests --examples --all-features — zero warnings
  • cargo fmt --check — clean
  • cargo test --lib 'channels::web' — 429 pass (incl. test_css_handler_returns_base_in_multi_tenant_mode)
  • cargo test -p ironclaw_gateway --lib — 60 pass
  • bash scripts/pre-commit-safety.sh — pass
  • node --check on full concat'd JS — pass
  • Manual smoke: cargo run gateway, open browser, click through every tab (reviewer)
  • Playwright E2E suite green on CI

🤖 Generated with Claude Code

…face modules

The gateway's static frontend had grown into two merge-conflict hotspots: a
6 887-line `style.css` and an 11 189-line `app.js`, each a catch-all for every
surface of the SPA. Any two PRs touching different tabs were likely to collide.

This change splits both files by surface/concern while preserving bytes and
behavior. `STYLE_CSS` and `APP_JS` in `crates/ironclaw_gateway/src/assets.rs`
now `concat!(include_str!(...))` the split pieces at compile time, so the
served `/style.css` and `/app.js` URLs are unchanged and the existing
workspace-overlay (`custom.css`) path still works. Cuts land on function /
block boundaries; `node --check` validates the concat. Admin assets move
under `static/admin/` for symmetry. Per-commit safety + CI workflow validate
the split files per-file instead of the old monolith.

- Styles split into 20 files under `static/styles/{base,layout}.css +
  styles/{components,primitives,surfaces}/*.css`
- JS split into 24 files under `static/js/{core,surfaces}/*.js`
- No URL, CSP, or behavioural change — pure file-layout refactor

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings April 19, 2026 15:08
@github-actions github-actions Bot added scope: channel/web Web gateway channel scope: ci CI/CD workflows scope: docs Documentation size: XL 500+ changed lines risk: medium Business logic, config, or moderate-risk modules contributor: core 20+ merged PRs labels Apr 19, 2026
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Refactors the IronClaw web gateway frontend by splitting the previously monolithic static/style.css and static/app.js into smaller per-surface / per-concern modules, while keeping the public /style.css and /app.js URLs stable via compile-time concatenation.

Changes:

  • Split gateway JS into static/js/core/*.js and static/js/surfaces/*.js, concatenated into APP_JS at compile time.
  • Split gateway CSS into static/styles/{base,layout,components,primitives,surfaces}/*.css, concatenated into STYLE_CSS at compile time.
  • Update docs/tooling/CI (pre-commit safety + workflow checks + internal docs) to point at and validate the new module layout; move admin assets under static/admin/.

Reviewed changes

Copilot reviewed 52 out of 56 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
src/channels/web/platform/static_files.rs Updates inline docs to reference new JS module path for layout config.
src/channels/web/CLAUDE.md Updates docs to reference split onboarding module path.
scripts/pre-commit-safety.sh Updates JS syntax validation from monolithic file to per-module checks.
crates/ironclaw_gateway/static/styles/surfaces/users.css Adds Users-tab CSS module.
crates/ironclaw_gateway/static/styles/surfaces/tool-permissions.css Adds Tool Permissions-tab CSS module.
crates/ironclaw_gateway/static/styles/surfaces/skills.css Adds Skills-tab CSS module.
crates/ironclaw_gateway/static/styles/surfaces/settings.css Adds Settings-tab CSS module.
crates/ironclaw_gateway/static/styles/surfaces/routines.css Adds Routines-tab CSS module.
crates/ironclaw_gateway/static/styles/surfaces/projects.css Adds Projects-tab CSS module.
crates/ironclaw_gateway/static/styles/surfaces/missions.css Adds Missions-tab CSS module.
crates/ironclaw_gateway/static/styles/surfaces/memory.css Adds Memory-tab CSS module.
crates/ironclaw_gateway/static/styles/surfaces/logs.css Adds Logs-tab CSS module.
crates/ironclaw_gateway/static/styles/surfaces/config.css Adds Config-tab CSS module.
crates/ironclaw_gateway/static/styles/surfaces/auth.css Adds Auth-screen CSS module.
crates/ironclaw_gateway/static/styles/surfaces/activity.css Adds Activity-tab CSS module.
crates/ironclaw_gateway/static/styles/primitives/toast.css Adds toast primitive CSS module.
crates/ironclaw_gateway/static/styles/layout.css Adds global layout CSS module.
crates/ironclaw_gateway/static/styles/components/markdown.css Adds markdown component CSS module.
crates/ironclaw_gateway/static/styles/base.css Adds base/reset CSS module.
crates/ironclaw_gateway/static/js/surfaces/users.js Adds Users surface JS module.
crates/ironclaw_gateway/static/js/surfaces/tool-permissions.js Adds Tool Permissions surface JS module (plus shared shortcut bindings).
crates/ironclaw_gateway/static/js/surfaces/skills.js Adds Skills surface JS module.
crates/ironclaw_gateway/static/js/surfaces/routines.js Adds Routines surface JS module.
crates/ironclaw_gateway/static/js/surfaces/memory.js Adds Memory surface JS module.
crates/ironclaw_gateway/static/js/surfaces/logs.js Adds Logs surface JS module.
crates/ironclaw_gateway/static/js/surfaces/chat.js Adds Chat surface JS module.
crates/ironclaw_gateway/static/js/core/widgets.js Moves widget + layout logic into a core module.
crates/ironclaw_gateway/static/js/core/tool-activity.js Moves tool activity rendering logic into a core module.
crates/ironclaw_gateway/static/js/core/routing.js Moves hash-routing + navigation helpers into a core module.
crates/ironclaw_gateway/static/js/core/render.js Moves markdown/rendering/sanitization helpers into a core module.
crates/ironclaw_gateway/static/js/core/init-auth.js Moves auth/bootstrap/API helper logic into a core module.
crates/ironclaw_gateway/static/js/core/gateway-tee.js Moves gateway status + TEE UI logic into a core module.
crates/ironclaw_gateway/static/js/core/bootstrap.js Establishes global state + theme bootstrap in a core module.
crates/ironclaw_gateway/static/js/core/activity-store.js Moves active-work state tracking into a core module.
crates/ironclaw_gateway/src/assets.rs Switches APP_JS/STYLE_CSS to compile-time concatenation; updates admin asset paths.
CLAUDE.md Updates repo-level docs to reference new onboarding module path.
.github/workflows/code_style.yml Updates CI JS syntax check to validate all split JS modules.
.claude/commands/trace.md Updates internal tracing docs to reflect split frontend layout.
.claude/commands/add-sse-event.md Updates internal guidance for adding SSE handlers/CSS in the split layout.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +111 to +116
if (mod && e.key >= '1' && e.key <= '5') {
e.preventDefault();
const tabs = engineV2
? ['chat', 'memory', 'projects', 'settings', 'jobs']
: ['chat', 'memory', 'routines', 'settings', 'jobs'];
const idx = parseInt(e.key) - 1;
Copy link

Copilot AI Apr 19, 2026

Choose a reason for hiding this comment

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

engineV2 is referenced when building the Mod+1-5 tab list, but the rest of the codebase uses engineV2Enabled. As written this will throw a ReferenceError the first time the shortcut path runs, breaking keyboard navigation. Switch this to the correct global (or derive from activeWorkStore if that’s the intended source).

Copilot uses AI. Check for mistakes.
@ilblackdragon ilblackdragon merged commit a35f9d9 into staging Apr 19, 2026
21 checks passed
@ilblackdragon ilblackdragon deleted the refactor/gateway-static-split branch April 19, 2026 15:12
Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request refactors the monolithic frontend by splitting app.js and style.css into modular components organized by core functionality and UI surfaces. It updates the asset compilation process, documentation, and pre-commit safety scripts to accommodate the new structure. Feedback identifies several issues, including undefined variable and function references (engineV2, autoGrow), inconsistent property access for engine settings, and a logic flaw in SSE event handling that leads to duplicate messages. Additionally, it was recommended to relocate core widget infrastructure from surface-specific files to a centralized core module.

Comment on lines +113 to +115
const tabs = engineV2
? ['chat', 'memory', 'projects', 'settings', 'jobs']
: ['chat', 'memory', 'routines', 'settings', 'jobs'];
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

high

The variable engineV2 is not defined in this scope. Based on the global definitions in bootstrap.js and usage in other modules (like routing.js), this should be engineV2Enabled.

Suggested change
const tabs = engineV2
? ['chat', 'memory', 'projects', 'settings', 'jobs']
: ['chat', 'memory', 'routines', 'settings', 'jobs'];
const tabs = engineV2Enabled
? ['chat', 'memory', 'projects', 'settings', 'jobs']
: ['chat', 'memory', 'routines', 'settings', 'jobs'];

if (input) {
input.value = 'Create a new project for me. I want to set up an autonomous workspace for: ';
input.focus();
autoGrow(input);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

high

The function autoGrow is not defined. It appears to be a typo for autoResizeTextarea, which is defined in history.js and used for similar purposes in chat.js.

Suggested change
autoGrow(input);
autoResizeTextarea(input);

Comment on lines +161 to +170
const streamingMsg = document.querySelector('.message.assistant[data-streaming="true"]');
if (streamingMsg) streamingMsg.removeAttribute('data-streaming');

_turnResponseReceived = true;
if (_doneWithoutResponseTimer) {
clearTimeout(_doneWithoutResponseTimer);
_doneWithoutResponseTimer = null;
}
finalizeActivityGroup();
addMessage('assistant', data.content);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

This logic will cause duplicate messages in the UI for streaming responses. When a response event arrives, it calls addMessage, which appends a new bubble to the chat. However, if the response was already being streamed via stream_chunk events, a bubble already exists (marked with data-streaming="true").

The code should check for an existing streaming message and update it in place instead of always adding a new one.

    // Finalize the response: update the streaming message if present, otherwise add a new one.
    const streamingMsg = document.querySelector('.message.assistant[data-streaming="true"]');
    if (streamingMsg) {
      streamingMsg.removeAttribute('data-streaming');
      const contentEl = streamingMsg.querySelector('.message-content');
      if (contentEl) {
        streamingMsg.setAttribute('data-raw', data.content);
        streamingMsg.setAttribute('data-copy-text', data.content);
        contentEl.innerHTML = renderMarkdown(data.content);
        upgradeStructuredData(contentEl);
      }
    } else {
      addMessage('assistant', data.content);
    }

    _turnResponseReceived = true;
    if (_doneWithoutResponseTimer) {
      clearTimeout(_doneWithoutResponseTimer);
      _doneWithoutResponseTimer = null;
    }
    finalizeActivityGroup();

Comment on lines +33 to +42
activeWorkStore.setEngineV2Enabled(!!data.engine_v2);
applyEngineModeUi();
refreshPersistentActivityBar();

// Update restart button visibility
restartEnabled = data.restart_enabled || false;
updateRestartButtonVisibility();

// Apply engine v2 / v1 tab visibility once.
if (!engineModeApplied) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

Inconsistent property access on the data object. Line 33 uses data.engine_v2 while line 42 uses data.engine_v2_enabled. This will likely result in engineV2Enabled being incorrectly set to false in one of the steps if the backend only provides one of these keys.

Suggested change
activeWorkStore.setEngineV2Enabled(!!data.engine_v2);
applyEngineModeUi();
refreshPersistentActivityBar();
// Update restart button visibility
restartEnabled = data.restart_enabled || false;
updateRestartButtonVisibility();
// Apply engine v2 / v1 tab visibility once.
if (!engineModeApplied) {
activeWorkStore.setEngineV2Enabled(!!data.engine_v2_enabled);
applyEngineModeUi();
refreshPersistentActivityBar();
// Update restart button visibility
restartEnabled = data.restart_enabled || false;
updateRestartButtonVisibility();
// Apply engine v2 / v1 tab visibility once.
if (!engineModeApplied) {
engineV2Enabled = !!data.engine_v2_enabled;

e.target.dataset.edited = e.target.value ? '1' : '';
});

// ==================== Widget Extension System ====================
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

The Widget Extension System logic (defining window.IronClaw, registerWidget, and IronClaw.api) is core infrastructure and should ideally reside in a core module (e.g., static/js/core/widgets.js) rather than a surface-specific module like config.js. This would improve maintainability and ensure core APIs are initialized predictably.

ilblackdragon added a commit that referenced this pull request Apr 20, 2026
Synthesizes recurring patterns from ~30 merged PRs, 147 bot review
comments (Copilot/Gemini), human reviews, and ~50 issues filed in the
past 2 weeks. Each rule cites the motivating PR/issue numbers.

New files:
- error-handling.md — silent-failure taxonomy (unwrap_or_default, .ok()?,
  poisoned caches), persist-then-reload atomicity, channel-edge error
  mapping. (#2526, #2633, #2653, #2673, #2546, #2407, #2408)
- agent-evidence.md — side-effect claims must cite tool evidence,
  empty-fast outputs are errors, external-effect tools must read back,
  setup UI round-trip. (#2544, #2580, #2582, #2541, #2545, #2411, #2543,
  #2586)
- lifecycle.md — discovery vs. activation, terminal auth rejection,
  list_installed vs. list_active, deactivation unwinds, snapshot
  rehydrate must re-validate. (#2556, #2557, #2558, #2564, #2419,
  PR #2617, PR #2631)

Extended:
- types.md — from_trusted boundary rule, validated-newtype template with
  shared validate(&str), serde(try_from) required for validated types,
  wire-stable enums (no Debug; serde alias for migrations), canonical
  wire-contract field naming. (PR #2685, #2681, #2687, #2678, #2669,
  #2665, #2683, #2702)
- safety-and-sandbox.md — every new ingress scans pre-transform/pre-
  injection, bounded resources (interners/streams/fan-out caps), cache
  keys must be complete. (#2491, #2676, #2470, #2633, #2673, #2710,
  PR #2702)
- review-discipline.md — PR scope discipline, guardrail scripts are
  code (regression tests, grouped-import parsing, CI has_code inclusion),
  absolute-path ban in committed docs, stale comments after refactors.
  (PR #2668, #2628, #2680, #2687, #2647, #2689, #2701)

All new files carry paths: frontmatter so they auto-load only on
matching files.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
ilblackdragon added a commit that referenced this pull request Apr 20, 2026
* docs(rules): add review-driven guidance for Claude Code

Synthesizes recurring patterns from ~30 merged PRs, 147 bot review
comments (Copilot/Gemini), human reviews, and ~50 issues filed in the
past 2 weeks. Each rule cites the motivating PR/issue numbers.

New files:
- error-handling.md — silent-failure taxonomy (unwrap_or_default, .ok()?,
  poisoned caches), persist-then-reload atomicity, channel-edge error
  mapping. (#2526, #2633, #2653, #2673, #2546, #2407, #2408)
- agent-evidence.md — side-effect claims must cite tool evidence,
  empty-fast outputs are errors, external-effect tools must read back,
  setup UI round-trip. (#2544, #2580, #2582, #2541, #2545, #2411, #2543,
  #2586)
- lifecycle.md — discovery vs. activation, terminal auth rejection,
  list_installed vs. list_active, deactivation unwinds, snapshot
  rehydrate must re-validate. (#2556, #2557, #2558, #2564, #2419,
  PR #2617, PR #2631)

Extended:
- types.md — from_trusted boundary rule, validated-newtype template with
  shared validate(&str), serde(try_from) required for validated types,
  wire-stable enums (no Debug; serde alias for migrations), canonical
  wire-contract field naming. (PR #2685, #2681, #2687, #2678, #2669,
  #2665, #2683, #2702)
- safety-and-sandbox.md — every new ingress scans pre-transform/pre-
  injection, bounded resources (interners/streams/fan-out caps), cache
  keys must be complete. (#2491, #2676, #2470, #2633, #2673, #2710,
  PR #2702)
- review-discipline.md — PR scope discipline, guardrail scripts are
  code (regression tests, grouped-import parsing, CI has_code inclusion),
  absolute-path ban in committed docs, stale comments after refactors.
  (PR #2668, #2628, #2680, #2687, #2647, #2689, #2701)

All new files carry paths: frontmatter so they auto-load only on
matching files.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* refactor(rules): split agent-evidence into prompt + code rule

agent-evidence.md mixed two concerns: runtime agent instruction (what
the LLM should do when concluding a turn) and code-enforcement rules
(what the dispatcher, engine, and tools must implement). Rules under
.claude/rules/ only guide Claude Code when editing the repo — the
runtime agent never reads them.

Splits the two:

- crates/ironclaw_engine/prompts/codeact_postamble.md — new section
  "Evidence before claiming side effects". Sits next to the existing
  "FINAL() answer quality" guidance; loaded via include_str! in
  executor/prompt.rs (no Rust change needed).
- .claude/rules/tool-evidence.md — renamed from agent-evidence.md,
  keeps only the code invariants (engine v2 side-effect gate,
  empty-fast ToolError::EmptyResult, external-effect tools must read
  back, setup UI round-trip).

Prompt tests pass unchanged; the postamble addition is pure text.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* prompt: tighten evidence rule to FINAL() claims only, not tool use

Live-test validation of the "Evidence before claiming side effects"
section (added in the prior commit) showed it inhibited legitimate
tool use. With the original wording, `zizmor_scan_v2` live-recording
timed out at 302s with zero responses; reverting the postamble
restored healthy behavior (88s run, 8 shell calls including
`cargo install zizmor` and full workflow analysis).

The original phrasing conflated two things: what the agent should
claim and what tools it should call. The rule is only about the
claim. Re-tunes the section to:

- Open with an explicit "this does not restrict tool calls" scope.
- Drop the "<1ms = failure" heuristic (too broad — normal tools like
  `tool_info(schema)` are legitimately fast).
- Drop the full enumeration of forbidden side-effect verbs; keep the
  rule narrower and clearer.
- Shorten the code example (remove redundant early-return).

Re-tuned run: agent is active (shell calls, real reasoning), live
recording completes in ~9s. The remaining test failure is a
pre-existing assertion bug (exact `t == "shell"` match against tool
strings that now carry arguments like `"shell(cmd)"`) — reproduces
with the old postamble too.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* test(live): fix tool-name assertions + re-record zizmor traces

The two `zizmor_scan*` live tests had four broken tool-name assertions
that silently failed to match: `tools.iter().any(|t| t == "shell")`
against a tool list that now contains `"shell(cmd)"` strings (tool
events carry args via `format_action_display_name` in
`src/bridge/router.rs`). Two of the four were negative assertions
checking for the absence of `tool_install` recovery loops — those
silently passed even when a recovery loop actually ran. `sandbox_live_e2e.rs:203`
already used the correct `t == "shell" || t.starts_with("shell(")`
pattern; applied it consistently to all four sites.

Verified live:

- `IRONCLAW_LIVE_TEST=1 cargo test --test e2e_live -- zizmor_scan --ignored --test-threads=1`
  → 2 passed, 0 failed, 51.78s. Agent installs and runs zizmor
  end-to-end, producing real findings (exit code 14, dangerous
  triggers, excessive permissions, etc.).

Traces re-recorded with the tuned postamble (commit 50d8517) and
scrubbed: replaced `/home/illia/.cargo/bin/zizmor` with
`/home/user/.cargo/bin/zizmor` per the developer-local-path ban in
`.claude/rules/review-discipline.md`. No credentials, PII, or
high-entropy secrets in either trace (only git SHAs from zizmor's
workflow analysis output).

Replay still passes: `cargo test --test e2e_live -- zizmor_scan --ignored`
→ 2/2 ok.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* test(replay): update zizmor_scan_v2 insta snapshot

The engine v2 replay-snapshot gate (`engine_v2_tests::snapshot_zizmor_scan_v2`)
failed against the re-recorded trace from 1691efe because the old
snapshot encoded a broken run:

- final_state: Failed
- Missing Assistant message role
- 3 issues: thread_failure (error), no_response (warning), llm_error (error)
- 6 tool calls that never produced a final answer

The new trace completes cleanly:

- final_state: Done
- System / User / Assistant roles present
- 1 issue: mixed_mode (info)
- 3 shell tool calls + successful `FINAL()` with real findings

The snapshot was pinning a regression. Regenerated with
`INSTA_UPDATE=always cargo test --test e2e_engine_v2 -- snapshot_zizmor_scan_v2`;
passes on replay.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs(rules): address PR #2714 review feedback

- review-discipline: reword "Doc Absolute Paths" as a review convention
  (pre-commit only scans .rs; the rule misleadingly claimed enforcement).
- safety-and-sandbox: broaden `paths:` frontmatter to include the actual
  ingress owners (`bridge`, `channels`, `workspace`, `agent`, engine
  crate) so the rule auto-loads where it applies.
- tool-evidence: mark the side-effect gate, empty-fast rule, and
  `unverified` flag as target/aspirational invariants — neither
  `ToolError::EmptyResult`, an `unverified` field on `ToolOutput`, nor a
  byte-count field on `ActionRecord` exist today. Point at concrete
  interim conventions (`ToolError::ExecutionFailed`, `unverified: true`
  in the JSON result body).
- types: scope "Validated newtypes must gate Deserialize" to *new*
  types, document the `CredentialName`/`ExtensionName` exception (they
  intentionally use `#[serde(transparent)]` + derived `Deserialize`
  under the `serde_does_not_revalidate` test). Clarify the
  `from_trusted` trust boundary (trusted = typed upstream, untrusted =
  raw JSON field even if the field *name* is "registry entry").
  Switch `new` template to `impl Into<String>` to avoid an unnecessary
  clone when an owned `String` is passed.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs(rules): simplify types.md + split doc-hygiene; address review round 2

- types: collapse two templates into one canonical validated-newtype
  shape. New types use `#[serde(try_from = "String")]` with a shared
  `validate(&str)` helper — no more dual "transparent for some /
  try_from for others" guidance. `CredentialName`/`ExtensionName` are
  documented as the sole legacy exception (locked in by the
  `serde_does_not_revalidate` test); new code must not copy their
  `transparent` + `from_trusted` pattern. Removes the long "Using
  `from_trusted` safely" section and the separate "Validated newtypes
  must gate Deserialize" subsection that contradicted the Don'ts list.
- doc-hygiene: new tiny rule file scoped to `**/*.md`, `**/*.py`,
  `docs/**` that carries the "no developer-local absolute paths in
  committed docs" convention. Removed from review-discipline.md where
  its `src/**/*.rs` scope meant the rule never loaded on the files it
  governed.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* test(live): match hyphenated tool-install in attempted_relevant_tool

The engine records `action_name` as the raw string the LLM emitted
(`crates/ironclaw_engine/src/executor/structured.rs:381`), and the
registry's lookup canonicalization only affects dispatch — not the
name that reaches `StatusUpdate::ToolStarted`. The two other predicates
in this file (`bad_recovery` at :420, `phase_b_recovery` at :531)
already defend against both forms; this one should too, for
consistency. Addresses PR #2714 review.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@henrypark133 henrypark133 mentioned this pull request Apr 21, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

contributor: core 20+ merged PRs risk: medium Business logic, config, or moderate-risk modules scope: channel/web Web gateway channel scope: ci CI/CD workflows scope: docs Documentation size: XL 500+ changed lines

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants