Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ The renderer is layered. **Runtime imports flow in one direction only** — a mo
- `utils/` is split by concern: `api`, `auth`, `core`, `forges`, `notifications`, `system`, `ui`.
- `types.ts`, `constants.ts`, and `icons/` are the leaf modules: shared types, shared values, and presentational icons. `types.ts` imports types from `utils/auth/types` and generated GitHub GraphQL types — those imports are type-only, so the direction rule does not apply. `icons/` sits below `utils`, so both `utils` and `components` may import it (a forge adapter declares its brand mark from here).
- `stores/` holds client state (Zustand): persisted `accounts` and `settings`, session `filters` and notification action failures.
- `stores/index.ts` is the public store facade for `hooks`, `components`, and `routes`. Lower layers (`utils`) import the specific store module instead (for example `stores/useFiltersStore`): the barrel re-exports every store and its dependencies, which would pull the account/auth/forge graph into leaf utilities and create cycles.
- `utils` and `stores` are **peers** — they may reference each other. Neither may import `hooks`, `components`, or `routes`.
- `components/` is grouped by feature (`notifications`, `filters`, `settings`, `login`, `fields`, `avatars`, `layout`, `metrics`, `primitives`).
- The composition root (`App.tsx`, `index.tsx`) is the only place allowed to wire every layer together.
Expand Down Expand Up @@ -78,4 +79,4 @@ Multi-forge support is adapter-based. Each forge implements the `ForgeAdapter` i

## Enforcement

The one-way module dependency direction is checked automatically: `vite.config.mts` defines per-layer `no-restricted-imports` overrides for the renderer, and `pnpm check` fails on a violation. The enforced rules are: `utils` and `stores` must not import `hooks`, `components`, or `routes`; `hooks` must not import `components` or `routes`; `components` must not import `routes`; and the leaf layers stay leaves — `icons` must not import any implementation layer, and the leaf modules `types`/`constants` must not import implementation layers either (their type-only imports are permitted). Any future exception must be argued and documented here rather than silently allow-listed.
The one-way module dependency direction is checked automatically: `vite.config.mts` defines per-layer `no-restricted-imports` overrides for the renderer, and `pnpm check` fails on a violation. The enforced rules are: `utils` and `stores` must not import `hooks`, `components`, or `routes`; `utils` must not import the `stores` barrel (it imports the specific store module); `hooks` must not import `components` or `routes`; `components` must not import `routes`; and the leaf layers stay leaves — `icons` must not import any implementation layer, and the leaf modules `types`/`constants` must not import implementation layers either (their type-only imports are permitted). Any future exception must be argued and documented here rather than silently allow-listed.
5 changes: 3 additions & 2 deletions src/renderer/utils/core/storage.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { Constants } from '../../constants';

import { DEFAULT_SETTINGS_STATE, useAccountsStore, useSettingsStore } from '../../stores';
import { sanitizeAccounts } from '../../stores/useAccountsStore';
import { DEFAULT_SETTINGS_STATE } from '../../stores/defaults';
import useAccountsStore, { sanitizeAccounts } from '../../stores/useAccountsStore';
import useSettingsStore from '../../stores/useSettingsStore';

import { rendererLogError, rendererLogInfo, toError } from './logger';

Expand Down
2 changes: 1 addition & 1 deletion src/renderer/utils/notifications/filters/reason.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useFiltersStore } from '../../../stores';
import useFiltersStore from '../../../stores/useFiltersStore';

import type {
AccountNotifications,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useFiltersStore } from '../../../stores';
import useFiltersStore from '../../../stores/useFiltersStore';

import type {
AccountNotifications,
Expand Down
2 changes: 1 addition & 1 deletion src/renderer/utils/notifications/filters/search.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useFiltersStore } from '../../../stores';
import useFiltersStore from '../../../stores/useFiltersStore';

import type { RawGitifyNotification } from '../../../types';

Expand Down
2 changes: 1 addition & 1 deletion src/renderer/utils/notifications/filters/state.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useFiltersStore } from '../../../stores';
import useFiltersStore from '../../../stores/useFiltersStore';

import type {
AccountNotifications,
Expand Down
2 changes: 1 addition & 1 deletion src/renderer/utils/notifications/filters/userType.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useFiltersStore } from '../../../stores';
import useFiltersStore from '../../../stores/useFiltersStore';

import type {
AccountNotifications,
Expand Down
2 changes: 1 addition & 1 deletion src/renderer/utils/notifications/group.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useSettingsStore } from '../../stores';
import useSettingsStore from '../../stores/useSettingsStore';

import type { GitifyNotification } from '../../types';

Expand Down
3 changes: 2 additions & 1 deletion src/renderer/utils/notifications/notifications.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { useAccountsStore, useSettingsStore } from '../../stores';
import useAccountsStore from '../../stores/useAccountsStore';
import useSettingsStore from '../../stores/useSettingsStore';

import type {
Account,
Expand Down
2 changes: 1 addition & 1 deletion src/renderer/utils/notifications/remove.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useSettingsStore } from '../../stores';
import useSettingsStore from '../../stores/useSettingsStore';

import type { Account, AccountNotifications, GitifyNotification } from '../../types';

Expand Down
2 changes: 1 addition & 1 deletion src/renderer/utils/system/comms.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import type {
TrayIconAppearance,
} from '../../../shared/events';

import { useSettingsStore } from '../../stores';
import useSettingsStore from '../../stores/useSettingsStore';

import { type Link, OpenPreference } from '../../types';

Expand Down
2 changes: 1 addition & 1 deletion src/renderer/utils/system/tray.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useSettingsStore } from '../../stores';
import useSettingsStore from '../../stores/useSettingsStore';

import { updateTrayColor, updateTrayTitle } from './comms';

Expand Down
9 changes: 9 additions & 0 deletions vite.config.mts
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,13 @@ const forgeSpecificBoundaryPattern = rendererBoundaryPattern(
'Renderer boundary: shared code must use the forge adapter instead of importing a forge-specific module (see ARCHITECTURE.md).',
);

const storeBarrelBoundaryPattern = {
group: ['**/stores', '**/stores/index', '**/stores/index.ts'],
allowTypeImports: true,
message:
'Renderer boundary: import the specific store module instead of the `stores` barrel (see ARCHITECTURE.md).',
};

export default defineConfig({
plugins: [
// only run the checker plugin in dev (not during `vite build`)
Expand Down Expand Up @@ -290,6 +297,7 @@ export default defineConfig({
),
processBoundaryPattern,
forgeSpecificBoundaryPattern,
storeBarrelBoundaryPattern,
],
},
],
Expand All @@ -311,6 +319,7 @@ export default defineConfig({
'Renderer boundary: `utils` may not import from hooks, components, or routes (see ARCHITECTURE.md).',
),
processBoundaryPattern,
storeBarrelBoundaryPattern,
],
},
],
Expand Down
Loading