architecture-vertical-slice
Vertical Slice Architecture — layers, import rules, and slice structure for src/main and src/preload
Install / Use
npx skills add AK2083/DojoSphereInstalls into whichever agent you are using.
.clinerules
Cline rules
Quality Score
Category
Development & EngineeringSupported Platforms
Tags
Skill content
View source on GitHubdescription: Vertical Slice Architecture — layers, import rules, and slice structure for src/main and src/preload globs: src/main//*,src/preload//* alwaysApply: false
Vertical Slice Architecture (Main & Preload)
The Electron main process and preload bridge use vertical slices — not FSD. The renderer follows architecture-fsd.md; main and preload are separate entry points at the same level under src/.
src/
renderer/ → FSD (UI, features, pages)
main/ → Vertical Slices (IPC, SQLite, domain logic)
preload/ → IPC bridge (contextBridge → window.api)
Layers
Imports point only inward — from app to features/shared, from features to shared. Features may call other features only via their public API (index.ts).
app → features, shared
features → shared (+ other features only via index.ts)
shared → internal only
window / preload → Electron shell (outside slices)
| Layer | Path | Responsibility |
| ------------ | ---------------------------- | ----------------------------------------------------------------------- |
| app | src/main/app/ | Composition root: bootstrap, IPC aggregation (register-ipc.ts) |
| features | src/main/features/<slice>/ | One use case / bounded context per slice |
| shared | src/main/shared/ | Infrastructure without business logic (database, security helpers) |
| window | src/main/window/ | BrowserWindow, renderer loading |
| preload | src/preload/ | contextBridge.exposeInMainWorld('api', …) — no SQL, no auth decisions |
app
Composition root of the main process — starts the app and wires slices.
bootstrap.ts— initialize database, open window, register IPCregister-ipc.ts— calls onlyregisterXxxIpc()from featureindex.ts- No SQL queries, no business logic — orchestration only
main.ts(entry) stays thin and delegates toapp/
features
Business functionality in slices — one slice = one cohesive use case in the main process (e.g. users, sessions, health).
Ideal slice structure:
src/main/features/<slice>/
ipc/
register.ts # ipcMain.handle — thin adapter, no SQL
service/ # use-case orchestration (optional)
repository/ # SQL access (optional)
index.ts # public API — sole export path for other slices
*.test.ts # tests colocated with code
Layering within a slice:
| Folder | Role |
| ----------------- | -------------------------------------------------------------------------------- |
| ipc/register.ts | Channel names, input validation, session check, delegation to service/repository |
| service/ | Coordinate multiple repositories/slices (e.g. create user + session) |
| repository/ | SQL via @main/shared/database — no electron, no ipcMain |
Existing slices (orientation): health, system, authorization, sessions, users.
- Cross-slice dependencies only via
@main/features/<slice>(index.ts), never via internal paths like another slice'srepository/…. - Add new functionality as a new slice; do not mix into existing slices.
shared
Infrastructure without feature affiliation. Features import only the public API.
| Segment | Content |
| --------------------------- | -------------------------------------------------------------------- |
| shared/database/ | SQLite port, runtime, migrations — see subdivision below |
| shared/database/driver.ts | Sole place with node:sqlite — do not import directly from features |
| shared/security/ | e.g. requireActiveSession() for privileged IPC handlers |
shared/database/ — recommended subdivision
shared/database is not a feature slice but technical infrastructure (port + adapter). Still, a clear split by responsibility, not domain, is worthwhile:
shared/database/
index.ts # public API — sole import path for features
types/
database.ts # Database, DatabaseStatement
migration.ts # Migration
connection.ts # initDatabase, getDatabase, closeDatabase, createMemoryDatabase
transactions.ts # runInTransaction
pragmas.ts # SQLite PRAGMAs
runner.ts # runMigrations
validate-schema.ts # schema assertions after migrations (no DDL)
driver.ts # SQLite adapter — sole import of node:sqlite
migrations/ # versioned .sql files + registry (index.ts)
| Segment | Responsibility | Imported by |
| ---------------------------------------------------------------- | ---------------------------------------- | ---------------------------------------------- |
| Types (types/) | Port interfaces | Driver, runtime modules, features (types only) |
| Driver (driver.ts) | Concrete SQLite binding | connection.ts only |
| Runtime (connection, transactions, pragmas) | Connection, transactions, PRAGMAs | app/bootstrap, features via public API |
| Migration (runner.ts, validate-schema.ts, migrations/) | Runner, schema assertions, versioned SQL | app/bootstrap |
Rules:
- Features/repositories import only
@main/shared/database(getDatabase,runInTransaction) — neverdriver.tsor other internal modules directly. - No domain-specific DDL outside
migrations/*.sql— tables live in versioned migrations (e.g.usersinV001__authorize_create_tables.sql).validate-schema.tsonly asserts, throws on incompatible legacy schema. - Schema ownership: SQL files named by slice/context (
authorize_*,users_*, …), registry inmigrations/index.tsaggregates all files. Optional later: SQL per feature (features/<slice>/migrations/) exported and collected in registry — only worthwhile when many slices maintain their own schema.
Slice vs. shared boundary:
| What | Where |
| --------------------------------------- | ---------------------------------------------- |
| CREATE TABLE users, columns, indexes | shared/database/migrations/*.sql |
| SELECT/INSERT/UPDATE per use case | features/<slice>/repository/ |
| Session check before write access | shared/security or slice ipc/ |
| Open DB, migrate, close | app/bootstrap + shared/database public API |
sharedimports no features and noelectronexcept where technically required (database path viaapp).- Tests use
@main/shared/database, not internal driver paths (driver.ts).
window
Electron window setup — outside feature slices.
main-window.ts—BrowserWindow, preload path, DevTools rulesload-renderer.ts— dev server vs.distfor the renderer- No domain logic, no IPC handling
preload
Lives at the same level as main/ and renderer/ (src/preload/).
- Exposes typed methods on
window.api(seesrc/renderer/shared/types/electron-api.ts) - Calls only
ipcRenderer.invoke— no privileged decisions, no SQLite - New IPC channels: extend
preload.ts+electron-api.ts+ main sliceipc/register.tstogether
Import rules
- features → shared — allowed (
@main/shared/database,@main/shared/security) - features → features — only via
@main/features/<slice>(index.ts) - shared → features — forbidden
- app → features — public APIs only (
registerXxxIpc, exported services) - Repositories — no
electron, noipcMain - IPC handlers — no SQL, no complex business logic (delegate to service/repository)
- Renderer — accesses main only via
window.api(preload), never main code directly
Security
- Authorization and session checks only in the main process (auth-security.md, security-privacy.md)
- Privileged IPC handlers: session (permissions later) via
@main/shared/securityor session slice - Store session tokens as hashes only — never plaintext
- SQLite only in the main process via
@main/shared/database
Import aliases
@main/app/*— composition root@main/features/<slice>— slice public API@main/shared/*— shared infrastructure
Preload imports types from @shared/types/electron-api (renderer shared types — intentional exception for the API contract layer).
Documentation
- All exported functions, types, and interfaces under
src/main/require English JSDoc. - Enforced via ESLint (
jsdoc/*onsrc/main/**/*.ts, excluding tests). - Internal helpers without
exportare documented only when non-obvious.
Renderer (FSD) vs. main (VSA)
| Aspect | Renderer (src/renderer/) | Main (src/main/) |
| -------------- | -------------------------- | ----------------------------------- |
| Architecture | Feature-Sliced Design | Vertical Slices |
| UI | ui/, Vue components | No UI (except window/) |
| Data access | IPC → window.api | SQLite via shared/database |
| Auth decisions | Never alone in renderer | In main process |
| Public API | index.ts per slice | index.ts per slice + IPC channels |
Checklist for new main slices
- Correct slice chosen — one use case, not a catch-all folder?
src/main/features/<slice>/index.tsas sole export surface?- IPC registered in
src/main/app/register-ipc.ts? - Preload +
electron-api.tsextended only for new IPC channels? - Schema change → migration under
src/main/shared/database/migrations/? - Tests colocated; DB tests via
@main/shared/database? - Privileged handlers secured with session check?
- Exported APIs documented with English JSDoc?
Related Skills
claude-howto
41.4kA visual, example-driven guide to Claude Code — from basic concepts to advanced agents, with copy-paste templates that bring immediate value.
ai-job-search
40.9kThe job search that runs on your machine. AI job application framework built on Claude Code: evaluate postings, tailor CVs, write cover letters, prep interviews. Fork it and own it.
guizang-ppt-skill
25.7kAI-agent Skill for generating polished HTML slide decks: editorial magazine and Swiss layouts, image prompts, social covers, and a WebGL/low-power presentation runtime.
reactive-resume
42.2kA one-of-a-kind resume builder that keeps your privacy in mind. Completely secure, customizable, portable, open-source and free forever. Try it out today!
Security Score
Audited on Invalid Date
