Skip to content

Bulletproof React features/ migration (phases 0-6)#45

Merged
revtex merged 15 commits into
mainfrom
dev
May 1, 2026
Merged

Bulletproof React features/ migration (phases 0-6)#45
revtex merged 15 commits into
mainfrom
dev

Conversation

@revtex
Copy link
Copy Markdown
Owner

@revtex revtex commented May 1, 2026

Internal frontend refactor — no user-visible changes.

Reorganizes frontend/src/ from a kind-first layout (components/, hooks/, pages/, app/slices/, services/, types/) into a feature-first layout under features/ + a single shared/ lowest layer, following Bulletproof React with two intentional deviations documented in .github/PROJECT_LAYOUT.md § 3:

  • Single shared app/api.ts (sub-features attach via api.injectEndpoints) instead of per-feature createApi.
  • Single shared/ instead of split lib/ + components/ + hooks/ + types/.

Phases

  • Phase 0: scaffold features/ and shared/ directories
  • Phase 1: move cross-feature code into shared/
  • Phase 2: extract auth and setup features
  • Phase 3: extract scanner and shared-call features
  • Phase 4: extract admin chrome into features/admin/_shell/
  • Phase 5.1–5.6: extract every admin sub-feature (legacy-usage, radio-reference, tools, options, dir-monitor, downstreams, webhooks, shared-links, groups-tags, transcription, api-keys, users, systems, logs, dashboards/activity)
  • Phase 6: rewrite PROJECT_LAYOUT.md § 3 to document the new layout

Enforcement

Eslint no-restricted-imports enforces dependency direction: pages → features → shared → app. Sibling features are opaque (cross-feature imports only via public barrels). Admin sub-features are opaque to each other, with one documented exception (features/admin/_shell).

Validation

  • npx tsc --noEmit — clean
  • npx eslint src — clean
  • pnpm test — 22 files / 201 tests pass
  • go vet ./... && go build ./... — clean

CHANGELOG [Unreleased] intentionally empty; next user-visible feature triggers the next release tag.

skip-changelog

OpenScanner Dev added 15 commits April 29, 2026 19:41
…l dumping ground

Per Bulletproof React conventions documented in .github/PROJECT_LAYOUT.md,
services/ should be feature-scoped, not contain a generic util/ bucket.
Renamed services/util/downloadFilename.ts -> services/download/filename.ts
and updated the two call sites.
- Create empty features/ and shared/ directories with .gitkeep placeholders
- Add ESLint no-restricted-imports rules enforcing the Bulletproof-style
  boundaries: pages/ -> features/ -> shared/ -> app/. Sibling features may
  only cross via their public barrels; shared/ cannot depend on features/.

No file moves yet. Subsequent phases populate features/ and shared/.

[skip-changelog]
- services/{audio,ws,download} -> shared/services/
- hooks/shared/{useTheme,useWebSocket} -> shared/hooks/
- types/{api,ws,ui,config} -> shared/types/
- Update all imports to @/shared/* paths
- Keep @/types and @/hooks/shared barrels as transitional shims

[skip-changelog]
Extract `features/auth/` and `features/setup/` per the Bulletproof React
migration plan — smallest cohesive feature first to validate the pattern.

Moves:
- app/slices/shared/authSlice.{ts,test.ts} → features/auth/
- hooks/shared/useAuthInit.{ts,test.tsx}   → features/auth/
- hooks/shared/useTokenRefresh.{ts,test.tsx} → features/auth/
- types/auth.ts                            → features/auth/types.ts
- pages/Login.{tsx,test.tsx}               → features/auth/
- pages/Setup.{tsx,test.tsx}               → features/setup/

Public barrels (`features/auth/index.ts`, `features/setup/index.ts`)
re-export the data layer (slice + types). Page modules and effect-hooks
are imported by main.tsx via direct paths to avoid a circular eager-load
through @/app/store. main.tsx gets a `no-restricted-imports: off`
override so it can wire routes without going through barrels.

Documented eslint debt: shared/services/ws/{client,adminClient}{,.test}.ts
and shared/hooks/useWebSocket.ts may import `@/features/auth` (barrel
only) until the auth-action dispatch is inverted via callbacks. Comment
in eslint.config.js tracks the follow-up.

Drops stale transitional barrels (hooks/index.ts, hooks/shared/index.ts)
and the `./auth` re-export from types/index.ts.

Validation: tsc clean, eslint clean, 22 test files / 201 tests pass,
backend go vet + build clean.

[skip-changelog]
Extract `features/scanner/` and `features/shared-call/` per the
Bulletproof React migration plan.

Moves:
- components/scanner/*  → features/scanner/components/*
- hooks/scanner/*       → features/scanner/hooks/*
- app/slices/scanner/{scannerSlice,callsSlice,shareSlice}{,.test}.ts
                        → features/scanner/
- types/call.ts         → features/scanner/types.ts
- pages/Scanner.tsx     → features/scanner/Scanner.tsx
- pages/SharedCall.{tsx,test.tsx}
                        → features/shared-call/

Public barrel `features/scanner/index.ts` re-exports the data layer
(scannerSlice + callsSlice + shareSlice + types). Components and
feature-internal hooks are not re-exported — nothing outside the
feature consumes them. Page module is imported directly by main.tsx.

`features/shared-call/index.ts` is empty (page imported directly).

Documented eslint debt expanded to cover scanner: shared/services/audio/
player.ts and shared/types/ws.ts may import the @/features/scanner
barrel for the Call/TranscriptionSegment types (they are logically
shared but live in features/scanner/types.ts per the plan). Same goes
for shared/services/ws/client.{ts,test.ts} which dispatches scanner
actions. Comment in eslint.config.js tracks the follow-up.

Drops the `./call` re-export from types/index.ts; consumers of Call /
TranscriptionSegment now import them from the scanner feature directly.

Validation: tsc clean, eslint clean, 22 test files / 201 tests pass,
backend go vet + build clean.

[skip-changelog]
Moves the admin page and its chrome into `features/admin/` per the
Bulletproof React migration plan.

Moves:
- pages/Admin.{tsx,test.tsx}        → features/admin/
- app/slices/admin/adminSlice.ts    → features/admin/_shell/
- hooks/admin/useAdminWebSocket.ts  → features/admin/_shell/
- hooks/admin/useAdminWsOps.ts      → features/admin/_shell/
- hooks/admin/useNavigationGuard.tsx → features/admin/_shell/
- hooks/admin/useWsQuery.ts         → features/admin/_shell/

The leading-underscore `_shell/` folder marks chrome that is shared
across admin sub-features but is not itself a feature. A new
`_shell/index.ts` barrel re-exports the slice and chrome hooks; admin
sub-features (Phase 5) will import from `@/features/admin/_shell` —
the eslint pattern is whitelisted to allow this single 3-segment
import path.

`features/admin/index.ts` is empty for now (Admin page imported
directly by main.tsx, mirroring the auth/setup/scanner pattern).

Activity / logs hooks (`useAdminActivity`, `useAdminLogs`) and
`activitySlice.ts` stay in their current location — they move into
`features/admin/dashboards/` (with TR-MQTT) and `features/admin/logs/`
in Phase 5. The transitional `hooks/admin/index.ts` barrel is slimmed
to re-export only those two.

Decomposing Admin.tsx into a `_shell/AdminTabs.tsx` chrome component
is intentionally deferred — the route table will be rewritten in
Phase 5 anyway as panels move into sub-feature barrels.

Validation: tsc clean, eslint clean, 22 test files / 201 tests pass,
backend go vet + build clean.

[skip-changelog]
…b-features (phase 5.1)

Moves:
- components/admin/LegacyUsageBanner.{tsx,test.tsx} → features/admin/legacy-usage/
- components/admin/RadioReferenceCard.tsx          → features/admin/radio-reference/

Each new sub-feature has an `index.ts` barrel with the standard
named-root-file convention (`export { default } from "./<Panel>";`).
Admin.tsx and ToolsPanel updated to import via the new barrels.

eslint `no-restricted-imports` extended: `@/features/admin/*`
(admin sub-feature barrels at depth 3) is now an explicit exception
to the generic depth-3 deep-import block, while the second pattern
still bans imports past the sub-feature barrel.

Validation: tsc clean, eslint clean, 22 test files / 201 tests pass.

[skip-changelog]
…in sub-features (phase 5.2)

Moves four single-panel admin sub-features (no slices, no tests):
- components/admin/ToolsPanel.tsx        → features/admin/tools/
- components/admin/OptionsPanel.tsx      → features/admin/options/
- components/admin/DirMonitorPanel.tsx   → features/admin/dir-monitor/
- components/admin/DownstreamsPanel.tsx  → features/admin/downstreams/

Each new sub-feature has the standard
`export { default } from "./<Panel>";` barrel. Admin.tsx imports
updated.

Validation: tsc clean, eslint clean, 22 test files / 201 tests pass.

[skip-changelog]
…iption admin sub-features (phase 5.3)

Moves four more single-panel admin sub-features (no slices, no tests):
- components/admin/WebhooksPanel.tsx       → features/admin/webhooks/
- components/admin/SharedLinksPanel.tsx    → features/admin/shared-links/
- components/admin/GroupsTagsPanel.tsx     → features/admin/groups-tags/
- components/admin/TranscriptionPanel.tsx  → features/admin/transcription/

Each new sub-feature uses the standard
`export { default } from "./<Panel>";` barrel. Admin.tsx imports
updated.

Validation: tsc clean, eslint clean, 22 test files / 201 tests pass.

[skip-changelog]
…ase 5.4)

Moves:
- components/admin/ApiKeysPanel.{tsx,test.tsx} → features/admin/api-keys/
- components/admin/UsersPanel.{tsx,test.tsx}   → features/admin/users/

New `index.ts` barrels re-export the default panel. Admin.tsx imports
each panel from its sub-feature barrel.

Validation: tsc clean, eslint clean, 22 test files / 201 tests pass,
backend go vet + build clean.

[skip-changelog]
…e 5.5)

Moves:
- components/admin/SystemsPanel.{tsx,test.tsx} → features/admin/systems/
- components/admin/LogsPanel.tsx               → features/admin/logs/
- hooks/admin/useAdminLogs.ts                  → features/admin/logs/

New `index.ts` barrels in each sub-feature; Admin.tsx imports panels
through them. The transitional `hooks/admin/index.ts` barrel is
slimmed to re-export only `useAdminActivity` (which moves with
activity into features/admin/dashboards/ as part of the TR-MQTT
plan).

Validation: tsc clean, eslint clean, 22 test files / 201 tests pass,
backend go vet + build clean.

[skip-changelog]
Move Activity into features/admin/dashboards/activity/ behind a thin DashboardsPanel wrapper, so the upcoming TR-MQTT integration (docs/plans/tr-mqtt-plan.md) can land as an additive sub-tab without renames or folder regroups.

- components/admin/ActivityPanel.tsx -> features/admin/dashboards/activity/ActivityPanel.tsx

- hooks/admin/useAdminActivity.ts    -> features/admin/dashboards/activity/useAdminActivity.ts

- app/slices/admin/activitySlice.ts  -> features/admin/dashboards/activity/activitySlice.ts

- New DashboardsPanel.tsx wrapper + barrel; Admin.tsx imports DashboardsPanel

- Removed transitional hooks/admin/index.ts and now-empty components/, hooks/, app/slices/ trees

Validated: tsc --noEmit clean, eslint clean, 22/201 vitest pass, go vet + build OK.

[skip-changelog]
…e 6)

Update PROJECT_LAYOUT.md \xc2\xa7 3 to match the post-migration tree:

- Replace pages/+components/+hooks/+app/slices/ description with features/ tree

- Document admin/_shell/ underscore-prefix convention and the eslint exception

- Document the two intentional Bulletproof deviations (single api.ts, single shared/)

- Rewrite \xc2\xa7 3.1 import rules around feature barrels and dependency direction

- Drop \xc2\xa7 3.2 pages-vs-components dichotomy; pages now live inside their feature

- Rewrite \xc2\xa7 3.3 state to put slices next to their feature

- Replace \xc2\xa7 3.4 folder-as-component with feature-folder layout + seam rules

- Move hooks (\xc2\xa7 3.5) and types (\xc2\xa7 3.6) to per-feature with shared/ fallbacks

- Note transitional @/types barrel

[skip-changelog]
@revtex revtex added the skip-changelog PR has no user-visible impact; skip CHANGELOG check label May 1, 2026
@revtex revtex merged commit fe0da21 into main May 1, 2026
19 of 20 checks passed
@revtex revtex deleted the dev branch May 1, 2026 03:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

skip-changelog PR has no user-visible impact; skip CHANGELOG check

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant