Skip to content

feat(automation): Automations panel with lifecycle, runs, and alerts#1053

Merged
Astro-Han merged 18 commits into
devfrom
claude/automation-pr6
Jun 2, 2026
Merged

feat(automation): Automations panel with lifecycle, runs, and alerts#1053
Astro-Han merged 18 commits into
devfrom
claude/automation-pr6

Conversation

@Astro-Han

@Astro-Han Astro-Han commented Jun 2, 2026

Copy link
Copy Markdown
Owner

Summary

Build the frontend Automations panel for issue #950 (frontend slice PR6). Adds a sidebar entry and a full-page surface to list, open, pause/resume, run-now, and delete automations, with run history, a repeated-failure alert, and global-sync data wiring. Also unhides the automate tool (removes the env gate) and narrows its agent-facing schema to the v1 surface, so chat can create automations that match the panel. The frozen PR1-5 backend contract is unchanged. Manual creation form, templates, and NL confirmation are intentionally out of scope (PR7).

Why

PR1-5 shipped the Automation domain, events, SDK, and scheduler, but the automate tool stayed gated and users had no way to see or manage automations. This PR is the management UI plus the gate flip, which makes the feature usable end to end.

Related Issue

#950

Human Review Status

Pending

Review Focus

  1. Slice E divergence from the Codex review. I narrowed the v1 surface only in the automate tool schema (drop context / recurring stop / where.worktree; inject context: fresh + recurring stop: never in execute) and left domain Automation.create/update validation UNCHANGED. Codex P1 fix(config): wait for background installs on dispose #3 also asked to reject those at the domain layer; I intentionally did not, because the contract is frozen (PR1-5 merged), the domain Stop keeps all three kinds on purpose for the HTTP/SDK/structured-error contract (automation/index.ts:68-71), and domain rejections would change frozen behavior and break merged tests. Enforcement lives in the tool/product layer.
  2. Read-only detail in v1. Project / Repeats / Model / Reasoning are displayed, not inline-editable; lifecycle actions (Run now / Pause / Delete) and run history are live. Inline editing depends on PR7's pickers and is deferred.
  3. Surface scope = current root directory (the opened project), matching "this project's automations"; list / events / run-session ownership all align to that root.
  4. Failure-alert rising edge (event-reducer.ts): gated on an accepted (revision-newer) update and a witnessed sub-threshold transition, so SSE replays and the bootstrap snapshot stay quiet. The prior streak is snapshotted as a primitive before the store write (the Solid store proxy would otherwise read back the post-write value).

Risk Notes

  • The automate tool is now always on (env gate removed); chat can create automations, with the schema narrowed to the v1 shape.
  • Domain validation intentionally still accepts context=continue / stop=count / where.worktree via raw API/SDK. No product surface emits them; PR7's create form will send explicit defaults (SDK types still require them).
  • No auto-pause on repeated failure — only a subtle toast on the rising edge of failureStreak >= 3.
  • Skipped conditional checklist items: macOS/Windows platform item (no platform/packaging/updater/signing/shell surface touched); docs/release-notes/deps item (no dependency, release-note, credential, or deletion surface in the PR diff).

How To Verify

app typecheck: OK
opencode typecheck: OK
app automation unit tests: 60 passed (automation-store; event-reducer incl. failure-streak rising edge; bootstrap; schedule)
opencode automate + registry tests: 37 passed
opencode automation-scheduler tests: 40 passed (tool -> create -> schedule path with injected v1 defaults)
E2E (Playwright, local) automations-panel.spec.ts: 2 passed (list -> detail -> pause -> delete; Escape unwind + sidebar stays live)
i18n en/zh parity for automations.*: clean
visual: bun run snap automations-surface -> grid under docs/design/preview/screenshots/automations-surface.png
Left to PR CI: full app / opencode / e2e suites

Screenshots or Recordings

Snap target automations-surface (Chromium web renderer) covers empty state, list, list hover (pause/resume action), and detail. Run bun run snap automations-surface to regenerate the grid. The change is web-renderable (no platform.* / preload / IPC paths), so dev:desktop was not required.

Checklist

How to use this checklist:

  • Tick a box by replacing [ ] with [x]. Do not edit, add, or remove items.
  • The bot-applied label items can only be honestly ticked AFTER the PR is opened and the labeler / priority-triage bots have run — return to the PR description and tick them then.
  • Most items are required. The few that are conditional are explicitly marked (conditional); for those, leave unticked if they truly do not apply and explain why in Risk Notes. All other items must be ticked before requesting human review.
  • Type label — this PR carries exactly one of bug, enhancement, task, documentation. Type labels are author-added; the labeler bot does NOT assign them. Add the label in the GitHub UI, then tick this.
  • Routing labels — this PR carries at least one of app, ui, platform, harness, ci. The labeler bot assigns these on PR open based on changed paths. Confirm the bot's choice (or override if wrong), then tick this.
  • Priority label — this PR carries exactly one of P0, P1, P2, P3. The priority-triage bot suggests one on PR open. Confirm or override, then tick this.
  • Human Review Status above is set to Pending, Approved by @<reviewer>, or Not required: <reason> (default is Pending; "not required" is restricted to bot-authored low-risk PRs).
  • I linked the related issue, or stated in Summary why there is no issue.
  • I described the review focus and any meaningful risks.
  • I replaced the example block in How To Verify with the real verification steps and the key result for each.
  • I did not introduce unrelated refactors, dependencies, generated files, or file changes beyond the stated scope.
  • (conditional) I manually checked visible UI or copy changes when needed, with screenshots or recordings. Leave unticked only if no visible UI or copy changed.
  • (conditional) I considered macOS and Windows impact for platform, packaging, updater, signing, paths, shell, or permissions changes. Leave unticked only if no platform/packaging surface was touched.
  • (conditional) I called out docs, release notes, dependencies, permissions, credentials, deletion behavior, generated content, or local file changes when relevant. Leave unticked only if none of those surfaces was touched.
  • I reviewed the final diff for unrelated changes and suspicious dependency changes.
  • I am targeting dev, and my PR title and commit messages use Conventional Commits in English.

Summary by CodeRabbit

  • New Features

    • Automations panel in the sidebar to view/manage scheduled tasks: list, detail, pause/resume, delete, and "Run now".
    • Human-friendly schedule summaries and next-run timestamps; run-state icons/labels; delete confirmation dialog.
    • Sidebar integration and Escape-key behavior to back out of detail or close the surface.
    • English and Chinese translations for Automations UI.
  • Tests

    • End-to-end and snapshot tests covering listing, detail, pause/resume, deletion, and UI states.

Astro-Han added 7 commits June 2, 2026 16:58
Add directory-scoped automation and run state to the child store, fed by
SSE events (definition.updated/deleted, run.updated) and the bootstrap
automation.list snapshot. Writes are gated on the monotonic revision with
tombstone fencing, so a route response that lands after a newer event is
ignored and reconnect re-fetches converge regardless of arrival order.
Expose a lazy automation.runs loader for the detail view.

Part of #950 PR6 (frontend automations panel), data-layer slice.
Generalize the layout shell surface state from a settings-only boolean to a
mutually-exclusive activeSurface (none | settings | automations). Settings keeps
its full sidebar+main takeover; automations only takes over the main area and
leaves the session sidebar live, so runs stay reachable in All chats.

Add an Automations entry to the sidebar top cluster (toggles the surface) and an
AutomationsSurface that reads the current project's definitions from the child
store: empty state, a read-only list with a humanized schedule summary, and a
minimal detail view. Includes a schedule-summary formatter with unit tests and
an automations-surface snap target (empty / list / detail).

Part of #950 PR6 (frontend automations panel).
Expose pause / resume / delete / runNow on the global-sync automation API. Each
mutation applies the authoritative response immediately (revision-gated) so the
UI updates without waiting for the SSE round-trip, and the matching event then
no-ops. Reveal a one-click pause/resume action on row hover (text button, no new
icon) with an error toast on failure. Extend the snap target with a hover shot.

Part of #950 PR6 (frontend automations panel).
…e actions

Replace the placeholder detail with the full read-only surface: title plus
Run now / Pause-Resume / Delete actions, an Instructions column, and a right
column with Status (active/paused, next run, last run), Details (project,
repeats, model, reasoning), and Previous runs. Run rows lazy-load via
automation.loadRuns on mount and open the run's chat session through the new
layout openAutomationRun helper. Delete confirms through a dialog and returns
to the list. Knobs stay read-only in v1; inline editing is deferred to PR7.
Remove the OPENCODE_ENABLE_AUTOMATE_TOOL env gate so the automate tool is
always available now that the Automations panel ships. Narrow the agent-facing
tool schema to the v1 surface: drop the context, recurring stop, and
where.worktree knobs, and inject the defaults (fresh context, never-stop) in
execute before the domain create parser.

The frozen domain contract (Automation.create/update validation) is left
untouched on purpose: it still accepts the full shape so HTTP routes, the SDK,
and the structured error contract keep working. Enforcing the v1 narrowing only
at the tool/product layer follows the "differentiation lives in the tool layer"
guidance and avoids changing behavior the merged PR1-5 tests depend on. This
diverges from the build-plan note that also asked for domain-level rejection.
Detect the rising edge of failureStreak >= 3 in the directory event reducer and
fire one subtle toast, no auto-pause. The edge is gated on an accepted
(revision-newer) update and a witnessed transition from below the threshold, so
SSE replays and the bootstrap snapshot (which never runs this reducer path) stay
quiet. The prior streak is snapshotted as a primitive before the store write
because the Solid store proxy would otherwise read back the post-write value.
Walk the real workflow in Playwright: open the panel from the sidebar, seed an
automation over the SDK, open its detail, pause it, and delete it through the
confirm dialog. A second spec asserts Escape unwinds detail to the list and then
closes the surface, and that opening Automations keeps the sidebar live (unlike
the Settings takeover). Add stable data-action hooks on the detail actions and
the delete-confirm button so the selectors do not ride on label text.
@Astro-Han Astro-Han added the enhancement New feature or request label Jun 2, 2026
@github-actions github-actions Bot added app Application behavior and product flows ui Design system and user interface harness Model harness, prompts, tool descriptions, and session mechanics labels Jun 2, 2026
@coderabbitai

coderabbitai Bot commented Jun 2, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@Astro-Han, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 59 minutes and 5 seconds. Learn how PR review limits work.

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

⌛ How to resolve this issue?

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

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

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 585f0d55-6d16-4ce5-a70b-efc3f65fcc5c

📥 Commits

Reviewing files that changed from the base of the PR and between fbb1c0d and a70b220.

📒 Files selected for processing (5)
  • packages/app/e2e/automations/automations-panel.spec.ts
  • packages/app/src/pages/layout.tsx
  • packages/app/src/pages/layout/pawwork-session-controller.ts
  • packages/opencode/src/tool/automate.ts
  • packages/opencode/test/tool/automate.test.ts
📝 Walkthrough

Walkthrough

Adds Automations UI: list/detail surface, delete dialog, schedule formatting, run-status icons, global-sync automation operations and SSE handling, revision-gated automation store and bootstrap merge, narrowed automate tool v1 input schema, i18n, and end-to-end + snapshot tests.

Changes

Automations Feature

Layer / File(s) Summary
Automation state types and revisioned store
packages/app/src/context/global-sync/types.ts, packages/app/src/context/global-sync/automation-store.ts, packages/app/src/context/global-sync/automation-store.test.ts
Adds automation, automation_run, automation_tombstone to State; provides predicates and applicators enforcing monotonic revisions; implements merge helpers for bootstrap reconciliation and run applicators.
Event reducer automation handling
packages/app/src/context/global-sync/event-reducer.ts, packages/app/src/context/global-sync/event-reducer.test.ts
Handles automation.definition.updated/deleted and automation.run.updated SSE events; detects rising-edge failure-streak (threshold=3) and exposes onAutomationFailureStreak callback; tests verify alert semantics and stale filtering.
Global sync automation operations
packages/app/src/context/global-sync.tsx
Adds automation.loadRuns, pause, resume, delete, and runNow that call SDK and apply authoritative responses into the child store; wires failure-streak toast.
Bootstrap hydration and child store init
packages/app/src/context/global-sync/bootstrap.ts, packages/app/src/context/global-sync/bootstrap.test.ts, packages/app/src/context/global-sync/child-store.ts
Bootstrap slow phase lists automations via SDK and merges with mergeAutomationList; child stores initialize automation maps; tests mock empty automation lists.
Schedule formatting and run status UI
packages/app/src/pages/automations/automation-schedule.ts, packages/app/src/pages/automations/automation-schedule.test.ts, packages/app/src/pages/automations/automation-run-status.tsx
Adds cron and interval humanization (limited cron shapes recognized), formatTimestamp, and RunStatusIcon mapping run states to labels/icons; includes tests for schedule outputs.
Automation list, detail, and delete flows
packages/app/src/pages/automations/automation-list.tsx, packages/app/src/pages/automations/automation-detail.tsx, packages/app/src/components/dialog-delete-automation.tsx, packages/app/src/pages/automations/automations-surface.tsx
AutomationList shows rows with schedule summaries and pause toggle; AutomationDetail shows instructions, status, next/last run, previous runs, and actions (run-now/pause/delete) gated by busy state; DialogDeleteAutomation confirms deletion; AutomationsSurface handles selection, Escape navigation, and error toasts.
Layout and shell wiring
packages/app/src/pages/layout.tsx, packages/app/src/pages/layout/layout-shell-frame.tsx, packages/app/src/pages/layout/pawwork-sidebar.tsx
Adds unified activeSurface state for settings/automations, integrates AutomationsSurface into shell overlays, updates sidebar header with automations button and active state, and coordinates portal visibility while surfaces are open.
Automate tool v1 surface and tests
packages/opencode/src/tool/automate.ts, packages/opencode/test/tool/automate.test.ts, packages/opencode/src/tool/registry.ts
Replaces prior union input with a flat scalar v1 AutomateParameters; execute derives timezone/model/variant/context/where/stop internally (uses nextCronFireAfter for oneshot). Tool registry now always exposes automate; tests updated to new shape and behaviors.
Localization
packages/app/src/i18n/en.ts, packages/app/src/i18n/zh.ts
Adds sidebar.pawwork.automations and automations.* keys for UI copy, actions, toasts, statuses, and delete confirmation.
E2E and snapshot tests
packages/app/e2e/automations/automations-panel.spec.ts, packages/app/e2e/snap/automations-surface.snap.ts
Adds smoke and Escape-key tests for automations panel lifecycle and a snapshot test composing empty, populated, hover, and detail screenshots.
Icons and small UI adjustments
packages/ui/src/components/icon.tsx, packages/app/src/components/session/session-header.tsx, packages/app/src/pages/layout/sidebar-items.tsx, packages/app/src/pages/session/right-panel-tab-strip.tsx
Adds pause icon; hides portalled titlebar/tab-strip/sidebar active states while automations surface is open to avoid conflicting active regions.

Sequence Diagram

sequenceDiagram
  participant User
  participant UI as AutomationsSurface
  participant GlobalSync
  participant SDK
  participant Store

  User->>UI: open automations surface
  UI->>GlobalSync: request automations list / subscribe SSE
  GlobalSync->>SDK: sdk.automation.list() / SSE events
  SDK->>Store: mergeAutomationList / applyAutomationDefinition/run/tombstone
  Store->>UI: automations state updated
  User->>UI: select automation row
  UI->>GlobalSync: automation.loadRuns(id)
  GlobalSync->>SDK: sdk.automation.run.list()
  SDK->>Store: merge runs
  User->>UI: click pause
  UI->>GlobalSync: automation.pause(id)
  GlobalSync->>SDK: pause API
  SDK->>Store: apply updated definition
  Store->>UI: status shows Paused
  User->>UI: click delete
  UI->>GlobalSync: automation.delete(id)
  GlobalSync->>SDK: delete API
  SDK->>Store: apply tombstone
  Store->>UI: row removed
Loading

🎯 4 (Complex) | ⏱️ ~75 minutes

Possibly related PRs

Poem

🐰 I seeded crons in midnight light,

paused and ran through dashboard night,
tombstones guard each revision's door,
lists and details hop once more,
tests cheer on the new UI sight.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 18.18% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed Title accurately summarizes the main change: adding an Automations panel with lifecycle management and alerts.
Description check ✅ Passed Description covers all required sections including Summary, Why, Related Issue, Human Review Status, Review Focus, Risk Notes, How To Verify, and most checklist items are completed.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/automation-pr6

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions github-actions Bot added the P2 Medium priority label Jun 2, 2026

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Suggested priority: P2 (includes user-path files (packages/app/src/components/dialog-delete-automation.tsx, packages/app/src/context/global-sync.tsx, packages/app/src/context/global-sync/automation-store.test.ts, packages/app/src/context/global-sync/automation-store.ts, packages/app/src/context/global-sync/bootstrap.test.ts, packages/app/src/context/global-sync/bootstrap.ts, packages/app/src/context/global-sync/child-store.ts, packages/app/src/context/global-sync/event-reducer.test.ts, packages/app/src/context/global-sync/event-reducer.ts, packages/app/src/context/global-sync/types.ts, packages/app/src/i18n/en.ts, packages/app/src/i18n/zh.ts, packages/app/src/pages/automations/automation-detail.tsx, packages/app/src/pages/automations/automation-list.tsx, packages/app/src/pages/automations/automation-run-status.tsx, packages/app/src/pages/automations/automation-schedule.test.ts, packages/app/src/pages/automations/automation-schedule.ts, packages/app/src/pages/automations/automations-surface.tsx, packages/app/src/pages/layout.tsx, packages/app/src/pages/layout/layout-shell-frame.tsx, packages/app/src/pages/layout/pawwork-sidebar.tsx)).

P1/P0 are reserved for maintainer confirmation. Please relabel manually if this is a release blocker, security issue, data-loss risk, or updater/runtime failure.

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

Copy link
Copy Markdown

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 introduces a comprehensive Automations panel to the application, allowing users to list, view details, pause, resume, run, and delete automated tasks. It includes UI components, global state synchronization, localization support, and E2E tests, alongside simplifying the automate tool parameters in the backend. The review feedback highlights several improvement opportunities: guarding formatTimestamp against invalid dates to prevent potential crashes, using createEffect instead of onMount in AutomationDetail for reactive run loading, replacing the custom basename helper with the standard getFilename utility, hoisting notifyFailure to avoid temporal dead zone issues, and correcting the non-standard Tailwind class opacity-55 to opacity-50.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread packages/app/src/pages/automations/automation-schedule.ts
Comment thread packages/app/src/pages/automations/automation-detail.tsx
Comment thread packages/app/src/pages/automations/automation-detail.tsx Outdated
Comment thread packages/app/src/pages/automations/automation-detail.tsx
Comment thread packages/app/src/pages/automations/automation-detail.tsx
Comment thread packages/app/src/pages/automations/automation-detail.tsx Outdated
Comment thread packages/app/src/pages/automations/automation-list.tsx

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 6

🧹 Nitpick comments (2)
packages/opencode/test/tool/automate.test.ts (1)

215-223: ⚡ Quick win

Assert the pinned v1 defaults in the echoed definition.

This is the main regression test for the v1-to-domain translation, but it never checks that recurring definitions are persisted with context: "fresh" and stop.kind: "never". Adding those expectations would lock down the behavior this PR is introducing.

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

In `@packages/opencode/test/tool/automate.test.ts` around lines 215 - 223, The
test in automate.test.ts currently asserts the echoed automationDefinition
fields but omits asserting the pinned v1 defaults for recurring definitions;
update the assertion on result.metadata.automationDefinition (the object under
test) to also include context: "fresh" and stop: { kind: "never" } so the test
verifies the v1-to-domain translation persists those defaults for recurring
automations (keep the other existing expectations like title, prompt, revision,
paused, where, and sourceSessionID).
packages/app/src/context/global-sync/bootstrap.test.ts (1)

43-45: ⚡ Quick win

Add a direct automation bootstrap assertion.

These changes only stub automation.list() so the new branch can run. There’s still no test proving that bootstrapDirectory() hydrates store.automation correctly from a non-empty response, so a regression in the mergeAutomationList(...) wiring would slip through unnoticed.

Also applies to: 156-156

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

In `@packages/app/src/context/global-sync/bootstrap.test.ts` around lines 43 - 45,
Stub automation.list() to return a non-empty list, call bootstrapDirectory(),
and add a direct assertion that store.automation contains the expected hydrated
entries; specifically ensure the test exercises bootstrapDirectory() and
verifies mergeAutomationList(...) actually populates the shared store by
asserting on store.automation (and/or its keys/values) after the call so the
wiring from automation.list() → mergeAutomationList → store.automation is
validated.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/app/src/context/global-sync.tsx`:
- Around line 347-369: The automation mutation helpers (pauseAutomation,
resumeAutomation, deleteAutomation, runAutomationNow) grab [store, setStore]
then await SDK calls without pinning the child, so if the child is disposed
while the request is in flight the response may be applied to a stale store;
update each function to pin the directory before making the async SDK call
(e.g., call children.pin(directory) after peek), perform the await, apply the
returned data with
applyAutomationDefinition/applyAutomationTombstone/applyAutomationRun using the
pinned store variables, and always unpin in a finally block
(children.unpin(directory)) to mirror the pattern used in loadAutomationRuns.

In `@packages/app/src/pages/automations/automation-schedule.ts`:
- Around line 9-13: The summary text in formatInterval wrongly rounds minutes to
the nearest hour; update function formatInterval (parameters everyMs, t) so it
only uses the hour label when the minute count divides evenly by 60 (i.e.,
minutes % 60 === 0); otherwise keep and display the exact minutes. Concretely,
compute minutes = Math.round(everyMs / 60000), if minutes < 60 return minutes
wording, else if minutes % 60 === 0 compute hours = minutes / 60 and return
hours wording, otherwise return the minutes wording (do not round to hours).
- Around line 25-29: The current early return treats any cron with hour === "*"
as "Hourly" even when minute isn't 0 (e.g., "*/15 * * * *"); change the check to
only match the exact hourly shape by requiring minute === "0" as well (i.e.,
replace the hour-only branch with a condition like if (hour === "*" && minute
=== "0") return t("automations.schedule.hourly")). Keep the existing integer
validation for minuteNum/hourNum to fall back to
t("automations.schedule.custom") for unsupported shapes.
- Around line 33-34: The weekly summary currently calls
t("automations.schedule.weekly", { time }) which omits which weekday runs;
change the call in the branch that matches /^[0-6]$/ to pass a localized day
name as an extra param (e.g. t("automations.schedule.weekly", { time, day:
weekdayName })) using your existing weekday/localization helper or
Intl.DateTimeFormat to derive weekdayName from dow, and then update the
translation strings for the key automations.schedule.weekly in en.ts and zh.ts
to include the {{day}} placeholder so labels render like "Weekly on {{day}} at
{{time}}".

In `@packages/app/src/pages/automations/automations-surface.tsx`:
- Around line 35-49: The Escape handler registered in onMount (function
onEscape) is added in the capture phase (document.addEventListener("keydown",
onEscape, true)) which can preempt sidebar menu handlers; change the listener to
the bubbling phase by removing/setting the third arg to false for both
addEventListener and removeEventListener so menu handlers run first, and
additionally guard inside onEscape by checking event.composedPath() or
event.target.closest(...) for transient menu elements (e.g.,
DropdownMenu/ContextMenu elements exposed by the sidebar) and return early if
the event originated inside those menus; keep the existing checks for
dialog/select overlays and the existing logic around selectedID(),
setSelectedID, and props.onClose.

---

Nitpick comments:
In `@packages/app/src/context/global-sync/bootstrap.test.ts`:
- Around line 43-45: Stub automation.list() to return a non-empty list, call
bootstrapDirectory(), and add a direct assertion that store.automation contains
the expected hydrated entries; specifically ensure the test exercises
bootstrapDirectory() and verifies mergeAutomationList(...) actually populates
the shared store by asserting on store.automation (and/or its keys/values) after
the call so the wiring from automation.list() → mergeAutomationList →
store.automation is validated.

In `@packages/opencode/test/tool/automate.test.ts`:
- Around line 215-223: The test in automate.test.ts currently asserts the echoed
automationDefinition fields but omits asserting the pinned v1 defaults for
recurring definitions; update the assertion on
result.metadata.automationDefinition (the object under test) to also include
context: "fresh" and stop: { kind: "never" } so the test verifies the
v1-to-domain translation persists those defaults for recurring automations (keep
the other existing expectations like title, prompt, revision, paused, where, and
sourceSessionID).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

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

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: a7ef76b3-9f89-4ee9-8db2-da5d0bdbd1ae

📥 Commits

Reviewing files that changed from the base of the PR and between 8be137a and 90994f3.

📒 Files selected for processing (27)
  • packages/app/e2e/automations/automations-panel.spec.ts
  • packages/app/e2e/snap/automations-surface.snap.ts
  • packages/app/src/components/dialog-delete-automation.tsx
  • packages/app/src/context/global-sync.tsx
  • packages/app/src/context/global-sync/automation-store.test.ts
  • packages/app/src/context/global-sync/automation-store.ts
  • packages/app/src/context/global-sync/bootstrap.test.ts
  • packages/app/src/context/global-sync/bootstrap.ts
  • packages/app/src/context/global-sync/child-store.ts
  • packages/app/src/context/global-sync/event-reducer.test.ts
  • packages/app/src/context/global-sync/event-reducer.ts
  • packages/app/src/context/global-sync/types.ts
  • packages/app/src/i18n/en.ts
  • packages/app/src/i18n/zh.ts
  • packages/app/src/pages/automations/automation-detail.tsx
  • packages/app/src/pages/automations/automation-list.tsx
  • packages/app/src/pages/automations/automation-run-status.tsx
  • packages/app/src/pages/automations/automation-schedule.test.ts
  • packages/app/src/pages/automations/automation-schedule.ts
  • packages/app/src/pages/automations/automations-surface.tsx
  • packages/app/src/pages/layout.tsx
  • packages/app/src/pages/layout/layout-shell-frame.tsx
  • packages/app/src/pages/layout/pawwork-sidebar.tsx
  • packages/opencode/src/tool/automate.ts
  • packages/opencode/src/tool/registry.ts
  • packages/opencode/test/tool/automate.test.ts
  • packages/opencode/test/tool/registry.test.ts

Comment thread packages/app/src/context/global-sync.tsx Outdated
Comment thread packages/app/src/pages/automations/automation-schedule.ts
Comment thread packages/app/src/pages/automations/automation-schedule.ts Outdated
Comment thread packages/app/src/pages/automations/automation-schedule.ts Outdated
Comment thread packages/app/src/pages/automations/automations-surface.tsx
Comment thread packages/opencode/src/tool/automate.ts Outdated
Astro-Han added 5 commits June 2, 2026 18:51
…ering

- formatInterval shows seconds for sub-minute cadences and only collapses to an
  hour label when the minutes divide evenly, so 30s and 90m stop rounding to
  "1 min" / "2 h"
- formatCron only labels a single fixed-minute, all-day cron as "Hourly";
  stepped minutes like */15 fall back to a custom schedule
- formatTimestamp returns "" for non-finite input instead of throwing a
  RangeError the timezone catch can't recover from
- detail view reuses the shared getFilename helper for the project label
…ations

- mergeAutomationList preserves definitions that arrived (via SSE) after the
  list request was issued, so a brand-new automation isn't reconciled away when
  a stale snapshot lands mid-flight
- pause/resume/delete/runNow pin the directory store around the in-flight SDK
  call so the authoritative response isn't applied to a disposed store
The capture-phase Escape handler now bails while a dropdown or context menu is
open, extending the existing dialog/select guard. Unlike the settings takeover,
the sidebar stays interactive over the Automations panel, so its menus must
dismiss on Escape instead of unwinding the panel.
The automate tool was the only tool whose LLM-facing schema used unions: a
top-level oneshot|recurring union plus a nested interval|cron rhythm union.
Models serialized those union nodes into JSON strings (`where` and `rhythm`
arrived as "{...}"), which failed decode, so the tool could not be called
reliably.

Collapse the surface to a flat struct — title, prompt, cron required;
recurring, timezone, model, variant optional — with zero union/anyOf nodes.
execute() translates it into the frozen Automation.CreateInput: project
defaults to the current instance, timezone to the host, model to the calling
session's model (else provider default), and recurring:false maps to a
one-shot whose fireAt is the next cron occurrence. Interval and sub-minute
cadence stay a UI/SDK feature, off the AI surface. The frozen domain contract
is unchanged.
…s open

Opening Automations only replaces the main region, so the session route stays
mounted and the sidebar row plus titlebar kept rendering the active session
next to the Automations nav item — two things looked selected at once. Gate
the titlebar's session title/folder and right-side panel controls on
automationsOpen, suppress the sidebar row's active highlight, and hide the
right-panel tab strip while the surface owns the main region. Also add the
missing tooltip to the sidebar Automations button.
Astro-Han added a commit that referenced this pull request Jun 2, 2026
#1060)

Pure extraction (layout governance slice 3) of the pawwork project/pinned
organization actions out of packages/app/src/pages/layout.tsx into
packages/app/src/pages/layout/pawwork-project-controls.ts.

What moved (verbatim, no behavior change):
- Pinned: togglePinnedSession, dragPawworkSession (cross-zone + intra-pinned reorder), movePinnedSessionByOne
- Project view: setPawworkSortMode, toggleProjectCollapsed, expandPawworkProjectGroup
- Project hide/rename: hideProject, unhideProject, handleRenameProject

Boundary:
- Exposed as createPawworkProjectControls(input) with reactive accessors
  (projects/sessions) and layout-owned renameProject/setWorkspaceName injected
  as deps. All 9 functions destructured back with identical names - every call
  site (sidebar render props, keyboard-nav expandPawworkProjectGroup, navigation
  helpers calling unhideProject) byte-unchanged. Inner param input->args in
  dragPawworkSession/movePinnedSessionByOne is a local rename only.
- Navigation wrappers (navigateToSession/navigateToProject/openPawworkHome/
  syncSessionRoute) intentionally stay in layout.tsx with the routing infra
  (slice 4). No DOM/copy/aria/command-id/storage-key/styling change.

Verification:
- bun turbo typecheck: 8/8 pass
- cd packages/app && bun test src/pages/layout: 158 pass / 0 fail
- /codex review (xhigh): no P0/P1/P2/P3 - confirmed reactive boundary preserved,
  imports all-unused, no TDZ, generic inference intact
- All CI checks green (36/36), mergeable CLEAN

Risk: low - mechanical extraction, layout.tsx 1704->1598 lines. Open PR #1053
(automation) also touches layout.tsx in non-overlapping regions and will rebase
after this merge (per repo owner).
Refs #1056, follow-up to #875.
Astro-Han added 2 commits June 2, 2026 22:56
Add hand-authored `play` and `pause` glyphs to the icon registry: pause
reuses circle-check's ring with thin bars; play is a rounded landscape
triangle stroked at 1.3 to match the family's measured weight (arrow-right
shaft 1.31, circle-check ring 1.04). Reorder the detail action buttons to
delete / pause / run-now, make pause an icon-only toggle (play when paused),
give run-now a leading play icon, and add breathing room (gap-6 outer,
gap-8 grid).
The e2e-smoke-tagging inventory test asserts the discovered @smoke titles
match a hardcoded list; the new automations-panel.spec.ts smoke case was
missing, failing unit-opencode. Add its entry in sorted position.
…ecent

P3: weekly cron summaries showed only the time, so `0 9 * * 1` (Mon) and
`0 9 * * 5` (Fri) rendered identically. Name the weekday via localized
strings (en/zh) so single-day schedules are distinguishable.

P2: the runs list loads only the most recent page and cannot page further,
so "Previous runs" over-promised. Rename it to "Recent runs" and note at the
mount call that dropping nextCursor is intentional.
Astro-Han added a commit that referenced this pull request Jun 2, 2026
…ut.tsx (#1061)

Slice 4 of the layout governance line (#1056). Pure extraction: moves the 5 routing actions (syncSessionRoute / navigateToProject / navigateToSession / openPawworkHome / openProject) plus deep-link handling (handleDeepLinks + the onMount window deepLinkEvent listener) out of pages/layout.tsx into the new controller pages/layout/pawwork-routing-actions.ts. layout.tsx 1598 -> 1530.

No user-visible behavior change (DOM/copy/aria/command IDs/storage keys/styles untouched). The functions are verbatim copies with an input. prefix; the controller takes minimal Pick<> capability slices as input.

Preserved behaviors (confirmed via /codex consult before implementing, then verified by /codex review):
- deep-link new-session goes through openProject(dir, false) + navigate to the ORIGINAL base64 directory slug, NOT openPawworkHome (which would resolve a different project root).
- syncSessionRoute keeps returning root (consumed by activeRoute.sessionProject dedup) and keeps the requestAnimationFrame/untrack ordering verbatim.
- navigateToSession keeps unhide-then-openSession ordering.

Boundary: projectRoot / activeProjectRoot / shellNavigation stay in layout.tsx and are injected as input — avoids the shellNavigation-serves-settings coupling and the projectRoot<->shellNavigation cycle. No TDZ risk: openProject is captured by the startup createResource closure but the fetcher awaits readiness before calling it (codex-confirmed).

Verification: bun run typecheck 8/8; cd packages/app && bun test src/pages/layout = 165 pass (incl. 7 new focused routing tests). /codex review GATE PASS (no P0/P1); 2 P2 test-quality findings fixed (cross-function call-order assertions + rAF scroll payload; resolved-root vs original-slug fixture).

Refs #1056. Note: #1053 (automation) also touches pages/layout.tsx and will rebase on top of this.
Resolve layout.tsx conflicts from dev's session-controller refactor:
- Drop removePinnedSessionIDs from the store import (dev removed its usage)
  and keep the AutomationsSurface import.
- dev moved loadSessionByID out of layout.tsx into pawwork-session-controller;
  expose it from the controller's return so openAutomationRun keeps async
  session loading.

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 2

🧹 Nitpick comments (2)
packages/opencode/test/tool/automate.test.ts (1)

12-20: 🏗️ Heavy lift

Use testEffect(...) for the new Effect-driven cases.

These new integration tests are all manual Effect.runPromise(...) calls with an ad hoc ctx(...) shim. In packages/opencode/test/**/*.test.ts, they should use const it = testEffect(...); with the tmpdir/git/instance setup here, the individual cases would then be it.live(...) instead of hand-rolled runtime plumbing. As per coding guidelines "Use testEffect(...) from test/lib/effect.ts for tests that exercise Effect services or Effect-based workflows."

Also applies to: 76-210

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

In `@packages/opencode/test/tool/automate.test.ts` around lines 12 - 20, Replace
the ad-hoc Effect.runPromise calls and the manual ctx(...) shim in this test
with the standard testEffect harness: import testEffect from
"test/lib/effect.ts", set const it = testEffect(...), and convert each test case
that currently calls Effect.runPromise(...) to use it.live(...) so the
tmpdir/git/instance fixtures and Effect runtime are managed consistently; update
references to the ctx(...) factory to use the testEffect-provided environment or
adapt ctx to be created per-case inside it.live callbacks so tests no longer
manually construct the runtime or abort signals.
packages/opencode/src/tool/automate.ts (1)

90-154: ⚡ Quick win

Name this tool entrypoint with Effect.fn(...).

This is a new Effect entrypoint in packages/opencode/src, but it is still an anonymous Effect.gen. Wrapping it in Effect.fn("Tool.automate.execute") keeps the behavior while restoring the repo’s tracing/naming convention. As per coding guidelines "Use Effect.fn("Domain.method") for named/traced effects and Effect.fnUntraced for internal helpers; these accept pipeable operators as extra arguments to avoid unnecessary outer .pipe() wrappers".

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

In `@packages/opencode/src/tool/automate.ts` around lines 90 - 154, The execute
entrypoint currently returns an anonymous Effect.gen; wrap that Effect.gen
invocation with a named Effect.fn to restore tracing (e.g., replace the current
Effect.gen(...) return value in the execute: (params, ctx) => ... block by
calling Effect.fn("Tool.automate.execute", /* the existing Effect.gen(...) */)
so the effect keeps identical behavior but gains the "Tool.automate.execute"
trace name; update the expression around the existing Effect.gen in the execute
function accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/opencode/src/tool/automate.ts`:
- Around line 66-79: The current sessionModel function swallows all exceptions
from MessageV2.stream; change the catch to only handle the "no persisted
messages" case by detecting the specific NotFoundError (or whatever concrete
error class/identifier the storage layer throws) and returning undefined for
that case, but rethrow any other errors so storage/corruption failures bubble
up; update the catch to check err instanceof NotFoundError (or err.name ===
'NotFoundError' if an instance check isn't available) and only return undefined
in that branch, otherwise throw err, keeping the rest of sessionModel and the
loop over MessageV2.stream unchanged.
- Around line 92-145: The timestamp "now" is captured too early (before async
model resolution/validation), causing race across cron boundaries; move sampling
of Date.now() to after the async/await path (i.e., after
validateModelAndVariantWith(provider, model, variant) returns) and before
computing the createInput and calling nextCronFireAfter and Automation.create so
both the oneshot fireAt and the { now } option passed to Automation.create use
the same fresh timestamp; update the code that declares/uses now (replace the
early const now = Date.now() with a new const now = Date.now() just before
building createInput / before Automation.create) and ensure
Automation.create(parsed, { now, sourceSessionID: ctx.sessionID }) still
receives that new now.

---

Nitpick comments:
In `@packages/opencode/src/tool/automate.ts`:
- Around line 90-154: The execute entrypoint currently returns an anonymous
Effect.gen; wrap that Effect.gen invocation with a named Effect.fn to restore
tracing (e.g., replace the current Effect.gen(...) return value in the execute:
(params, ctx) => ... block by calling Effect.fn("Tool.automate.execute", /* the
existing Effect.gen(...) */) so the effect keeps identical behavior but gains
the "Tool.automate.execute" trace name; update the expression around the
existing Effect.gen in the execute function accordingly.

In `@packages/opencode/test/tool/automate.test.ts`:
- Around line 12-20: Replace the ad-hoc Effect.runPromise calls and the manual
ctx(...) shim in this test with the standard testEffect harness: import
testEffect from "test/lib/effect.ts", set const it = testEffect(...), and
convert each test case that currently calls Effect.runPromise(...) to use
it.live(...) so the tmpdir/git/instance fixtures and Effect runtime are managed
consistently; update references to the ctx(...) factory to use the
testEffect-provided environment or adapt ctx to be created per-case inside
it.live callbacks so tests no longer manually construct the runtime or abort
signals.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

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

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: a2392f69-bfa9-4727-b813-103b86559dba

📥 Commits

Reviewing files that changed from the base of the PR and between 90994f3 and fbb1c0d.

📒 Files selected for processing (22)
  • packages/app/src/components/session/session-header.tsx
  • packages/app/src/context/global-sync.tsx
  • packages/app/src/context/global-sync/automation-store.test.ts
  • packages/app/src/context/global-sync/automation-store.ts
  • packages/app/src/context/global-sync/bootstrap.ts
  • packages/app/src/context/shell-surface.tsx
  • packages/app/src/i18n/en.ts
  • packages/app/src/i18n/zh.ts
  • packages/app/src/pages/automations/automation-detail.tsx
  • packages/app/src/pages/automations/automation-schedule.test.ts
  • packages/app/src/pages/automations/automation-schedule.ts
  • packages/app/src/pages/automations/automations-surface.tsx
  • packages/app/src/pages/layout.tsx
  • packages/app/src/pages/layout/pawwork-sidebar.tsx
  • packages/app/src/pages/layout/sidebar-items.tsx
  • packages/app/src/pages/session/right-panel-tab-strip.tsx
  • packages/opencode/src/automation/derived.ts
  • packages/opencode/src/tool/automate.ts
  • packages/opencode/test/config/e2e-smoke-tagging.test.ts
  • packages/opencode/test/server/automation-scheduler.test.ts
  • packages/opencode/test/tool/automate.test.ts
  • packages/ui/src/components/icon.tsx
✅ Files skipped from review due to trivial changes (3)
  • packages/app/src/context/shell-surface.tsx
  • packages/opencode/test/config/e2e-smoke-tagging.test.ts
  • packages/app/src/i18n/en.ts
🚧 Files skipped from review as they are similar to previous changes (9)
  • packages/app/src/pages/automations/automation-schedule.test.ts
  • packages/app/src/context/global-sync/bootstrap.ts
  • packages/app/src/pages/automations/automations-surface.tsx
  • packages/app/src/context/global-sync.tsx
  • packages/app/src/i18n/zh.ts
  • packages/app/src/pages/layout.tsx
  • packages/app/src/context/global-sync/automation-store.test.ts
  • packages/app/src/pages/layout/pawwork-sidebar.tsx
  • packages/app/src/pages/automations/automation-schedule.ts

Comment thread packages/opencode/src/tool/automate.ts
Comment thread packages/opencode/src/tool/automate.ts Outdated
Astro-Han added a commit that referenced this pull request Jun 2, 2026
…1062)

Slice 5 of the layout governance line (#1056). Pure extraction: moves the
workspace lifecycle actions (createWorkspace / createCurrentWorkspace /
deleteWorkspace / resetWorkspace / renameWorkspace / toggleCurrentWorkspace)
out of pages/layout.tsx into pages/layout/pawwork-workspace-lifecycle.ts.
layout.tsx 1530 -> 1358.

No user-visible behavior change (DOM / copy / aria / command IDs / storage keys
/ styles untouched). Functions are verbatim copies with an input. prefix; the
controller takes minimal Pick<> capability slices as input.

Boundary decisions:
- setWorkspaceName / renameProject stay in layout.tsx (already injected into the
  slice-3 project-controls controller) and are injected here as shared primitives.
- clearWorkspaceTerminals is injected, not imported. Importing @/context/terminal
  pulls @solidjs/router, which evals a client-only solid template at module load
  and breaks the controller's unit test under the server build. The exact type is
  recovered via a type-only `typeof import("@/context/terminal").clearWorkspaceTerminals`
  so no runtime module load enters the controller graph.
- The dead closeProject / toggleProjectWorkspaces (zero references across packages)
  are left untouched; this slice is pure extraction, not cleanup.

Verification: bun run typecheck 8/8; cd packages/app && bun test = 1667 pass
(incl. 14 new focused lifecycle tests covering rename no-op guard; create
dedup/child/navigate + failure abort; delete prune/reopen + navigate-before-remove
ordering + leaveDeletedWorkspace early-return + failure no-mutate; reset
clear-before-dispose-before-reset ordering + archive-only-unarchived + toast
dismiss on both success and failure; toggle git/non-git; createCurrent dispatch).

/codex review (xhigh): fixed [P1] — client-action-source.test.ts provenance guard
followed the moved workspace.reset header-tagging from layout.tsx to the new file;
fixed 2 [P2] — added the leaveDeletedWorkspace dialog-path test and pinned the
reset failure-branch toaster.dismiss. gemini thread (centralize filter / optional
chaining) resolved as out-of-scope for a verbatim extraction.

CI: 10/10 required green. The non-required perf-probe-baseline advisory failed on
a 60s homepage-cold timeout while capturing the BASE-branch baseline (not this
PR's code) — environmental flake, rerun triggered.

Refs #1056. #1053 (automation) also touches pages/layout.tsx and will rebase on top.
Astro-Han added 2 commits June 3, 2026 00:34
…utton

The pause/resume action is icon-only now (icon + aria-label, no text node), so
the e2e smoke's toHaveText("Resume") resolved to "" and failed e2e-artifacts.
Assert the aria-label instead, matching the button's accessible name.
…tion

sessionModel() caught every error from MessageV2.stream and returned undefined,
silently downgrading a corrupt-store/IO failure to the provider default model.
Only swallow NotFoundError (missing session); rethrow everything else.

One-shot creation sampled now before the async model resolution/validation, so
across a cron boundary that stale instant could compute an already-due fireAt.
Sample now after validation, just before building the create input.

Adds unit coverage: a non-NotFound stream failure fails the tool without
creating an automation, NotFound still falls back to the default model, and a
clock crossing a minute boundary during validation pushes the one-shot fireAt
to the next match.
Astro-Han added a commit that referenced this pull request Jun 2, 2026
Slice 6 of the layout governance line (#1056): pure extraction of the two workspace dialog components out of pages/layout.tsx.

- Moved DialogDeleteWorkspace and DialogResetWorkspace verbatim into a new factory pages/layout/pawwork-workspace-dialogs.tsx → createPawworkWorkspaceDialogs(input).
- Capabilities injected via a narrow input: globalSDK (Pick client), dialog (Pick close), language ({t}), params ({dir?}), currentDir, navigate, deleteWorkspace, resetWorkspace. No extra dialog powers (e.g. show) leaked in.
- Preserved behavior exactly: delete's leaveDeletedWorkspace = !!params.dir && workspaceKey(currentDir())===workspaceKey(directory) with navigate→close→deleteWorkspace ordering; file.status onMount dirty probe; reset's close→resetWorkspace ordering and active-session filter (archived === undefined only).
- Removed now-unused imports from layout.tsx (Dialog @opencode-ai/ui/dialog, Button @opencode-ai/ui/button); confirmed no residual references.
- Named the factory createPawworkWorkspaceDialogs / PawworkWorkspaceDialogsInput for consistency with the file name and the sibling createPawworkWorkspaceLifecycle.

No user-visible behavior / DOM / copy / aria / command ID / storage key / style changes.

Verification: bun run typecheck (8/8), eslint, layout unit tests; codex custom-focus review PASS (no P0/P1/P2, independently re-verified ordering, injection boundary, import cleanup, naming, tsgo --noEmit).

pages/layout.tsx 1234 → ~1086. Touches a file also occupied by open PR #1053 (rebase expected per #950 plan).

Refs #1056.
@Astro-Han Astro-Han merged commit 114c14d into dev Jun 2, 2026
34 checks passed
@Astro-Han Astro-Han deleted the claude/automation-pr6 branch June 3, 2026 02:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

app Application behavior and product flows enhancement New feature or request harness Model harness, prompts, tool descriptions, and session mechanics P2 Medium priority ui Design system and user interface

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant