Skip to content

refactor(renderer): codify module boundaries and decompose notification state - #3410

Merged
setchy merged 4 commits into
mainfrom
refactor/renderer-architecture
Oct 14, 2026
Merged

setchy merged 4 commits into
mainfrom
refactor/renderer-architecture

Conversation

@setchy

@setchy setchy commented Oct 14, 2026 •

Copy link
Copy Markdown
Member

Summary

Two related pieces of renderer cleanup, both included in this PR — nothing is deferred to a follow-up.

  1. Document and enforce the renderer's module boundaries.
  2. Decompose the notification hook.

Behaviour-preserving, with one deliberate exception: a stale-UI fix in AppearanceSettings (called out below).

1. Architecture — documented and enforced

  • Adds ARCHITECTURE.md: the one-way layer direction (runtime imports are one-way; type-only imports are exempt), state ownership per kind, the single-host side-effect pattern, and the forge adapter seam. Linked from CONTRIBUTING.md.
  • Enforces the layer direction with oxlint no-restricted-imports overrides in vite.config.mts (scoped to source files). utils and stores are treated as peer domain layers, since they reference each other today. The leaf layers are enforced too: icons cannot import any implementation layer, and types/constants cannot either (type-only imports are allowed, matching the doc).
  • Moves every icon component into a leaf src/renderer/icons/, so lower layers can import them with no lint exception.
  • Fixes the non-reactive hasMultipleAccounts selector in AppearanceSettings: it selected the accessor instead of invoking it, so the account-header checkbox could stay stale when the account count changed. It now updates reactively, with a regression test. This is the one user-visible behaviour change in the PR.

2. Notification state — decomposed

  • Adds pure utils/notifications/status.ts (deriveStatus, deriveGlobalError, hasNoAccountErrors), consumed by both the route and the hook.
  • Splits useNotifications into hooks/notifications/:
    • useNotificationsQuery — read side (optional polling observer)
    • useNotificationMutations — mark-as-read / done / unsubscribe
    • useNotificationEffects — sound/native alerts, wake/focus refetch, failure pruning
    • useNotificationsHost — owns the polling observer plus the effects; mounted once from GlobalEffects
  • Removes the withSideEffects flag; side-effect ownership is now structural. The public useNotifications() reader/action API is unchanged.

Notes for reviewers

  • utils ↔ stores coupling is documented as a deliberate peer relationship rather than forced into a strict order; breaking that coupling is a separate refactor.
  • The global test mock now also stubs useNotificationsHost (mirroring the existing useNotifications mock); useNotifications.test.tsx opts back into the real host via vi.unmock.

Verification

  • tsc --noEmit — clean
  • pnpm check (format + lint) — clean
  • pnpm test — 187 files, 1899 tests passing

Out of scope (separate changes, not in this PR)

Captured as their own OpenSpec changes, not part of this PR:

  • Moving the shortcut-registration Zustand store out of hooks/ into stores/.
  • Extracting the notification list builders (buildAccountItems, buildRepositoryItems) out of NotificationList.tsx.
  • Extracting login validation / error summarization out of the login component and route files.

…on state

Make the renderer's architecture explicit and enforced, then split the
single notification hook into focused modules.

- Add ARCHITECTURE.md documenting the one-way layer direction, state
  ownership per kind, and the single-host side-effect pattern; point to it
  from CONTRIBUTING.md
- Enforce the layer direction with oxlint no-restricted-imports overrides,
  treating `utils` and `stores` as peer domain layers
- Move all icon components into a leaf `src/renderer/icons/` so lower
  layers can import them without an exception
- Fix the non-reactive `hasMultipleAccounts` selector in AppearanceSettings
- Add pure `utils/notifications/status.ts` derivation and consume it from
  the route and the hook
- Split `useNotifications` into query / mutations / effects / host hooks
  and remove the `withSideEffects` flag; the public reader API is unchanged

Verified with tsc, oxlint/format checks, and the full test suite.
@github-actions github-actions Bot added the refactor Refactoring of existing feature label Oct 14, 2026
@setchy
setchy marked this pull request as ready for review October 14, 2026 04:02
@setchy
setchy requested a review from afonsojramos as a code owner October 14, 2026 04:02
@setchy
setchy requested a lite review from Copilot October 14, 2026 04:05

Copilot AI 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.

🟡 Changes recommended

The icons boundary enforcement gap remains unresolved, along with two review nits.

1 open finding
What changed in this PR

Refactors renderer module boundaries and decomposes notification state management while preserving the public notification API.

Changes:

  • Adds architecture documentation and lint boundary rules.
  • Moves icons into a dedicated renderer layer.
  • Splits notification querying, mutations, effects, and hosting.
  • Extracts notification status helpers and fixes reactive account selection.

Review notes:

  • Moderate: vite.config.mts does not enforce boundaries for src/renderer/icons/**.
  • Nit: ARCHITECTURE.md conflicts with existing type-only dependencies.
  • Nit: AppearanceSettings changes visible checkbox behavior, conflicting with the no-behavior-change description.
File Reviewed change
vite.config.mts Adds renderer boundary lint rules.
src/​renderer/​utils/​ui/​icons.ts Updates icon imports.
src/​renderer/​utils/​notifications/​status.ts Adds notification status derivation.
src/​renderer/​utils/​notifications/​status.test.ts Tests status helpers.
src/​renderer/​utils/​forges/​gitlab/​adapter.ts Updates GitLab icon usage.
src/​renderer/​utils/​forges/​gitlab/​adapter.test.ts Updates GitLab icon imports.
src/​renderer/​utils/​forges/​bitbucket/​adapter.ts Updates Bitbucket icon usage.
src/​renderer/​utils/​forges/​bitbucket/​adapter.test.ts Updates Bitbucket icon imports.
src/​renderer/​routes/​Notifications.tsx Uses shared notification status logic.
src/​renderer/​routes/​Login.tsx Updates logo import.
src/​renderer/​routes/​AccountScopes.tsx Updates scope icon import.
src/​renderer/​icons/​VolumeUpIcon.tsx Relocates volume-up icon.
src/​renderer/​icons/​VolumeDownIcon.tsx Relocates volume-down icon.
src/​renderer/​icons/​ScopeStatusIcon.tsx Relocates scope status icon.
src/​renderer/​icons/​ScopeStatusIcon.test.tsx Updates scope icon tests.
src/​renderer/​icons/​LogoIcon.tsx Relocates logo icon.
src/​renderer/​icons/​LogoIcon.test.tsx Updates logo icon tests.
src/​renderer/​icons/​GitLabIcon.tsx Adds GitLab leaf icon.
src/​renderer/​icons/​BitbucketIcon.tsx Adds Bitbucket leaf icon.
src/​renderer/​icons/​__snapshots__/​ScopeStatusIcon.test.tsx.snap Updates snapshots.
src/​renderer/​icons/​__snapshots__/​LogoIcon.test.tsx.snap Updates snapshots.
src/​renderer/​hooks/​useNotifications.ts Exposes the composed notification API.
src/​renderer/​hooks/​useNotifications.test.tsx Updates notification hook tests.
src/​renderer/​hooks/​notifications/​useNotificationsQuery.ts Handles notification queries.
src/​renderer/​hooks/​notifications/​useNotificationsHost.ts Owns the notification host.
src/​renderer/​hooks/​notifications/​useNotificationMutations.ts Handles notification mutations.
src/​renderer/​hooks/​notifications/​useNotificationEffects.ts Handles notification side effects.
src/​renderer/​components/​Sidebar.tsx Updates logo import.
src/​renderer/​components/​settings/​SystemSettings.tsx Updates volume icon imports.
src/​renderer/​components/​settings/​AppearanceSettings.tsx Fixes reactive account selection.
src/​renderer/​components/​settings/​AppearanceSettings.test.tsx Covers account selection behavior.
src/​renderer/​components/​GlobalEffects.tsx Mounts the notification host.
src/​renderer/​__helpers__/​vitest.setup.ts Mocks the notification host.
CONTRIBUTING.md Links architecture guidance.
ARCHITECTURE.md Documents renderer architecture and dependencies.

🧠 Review effort: Lite


💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread ARCHITECTURE.md Outdated
Co-authored-by: setchy <386277+setchy@users.noreply.github.com>
Complete the boundary rule for the leaf layers introduced with the
architecture documentation.

- Add no-restricted-imports overrides so `icons` may not import any
  implementation layer, and `types`/`constants` may not import
  implementation layers (type-only imports allowed)
- Drop the dead `**/context/**` group from the sortImports config, since
  AppContext is retired

Verified with tsc, oxlint/format checks, and the full test suite.
State the layer rule as "runtime imports are one-way; type-only imports are
exempt" and list types/constants/icons together as the leaf modules, so the
diagram no longer contradicts types.ts's type-only imports.
@setchy
setchy merged commit bd0e08d into main Oct 14, 2026
13 checks passed
@setchy
setchy deleted the refactor/renderer-architecture branch October 14, 2026 10:37
@github-actions github-actions Bot added this to the Release 7.10.0 milestone Oct 14, 2026
@sonarqubecloud

Copy link
Copy Markdown

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

refactor Refactoring of existing feature

Development

Successfully merging this pull request may close these issues.

3 participants