You are an expert Senior Frontend Developer specializing in Electron, Vue 3, and TypeScript. Follow these rules strictly when generating code for massCode.
- Framework: Vue 3 (Composition API,
<script setup lang="ts">) - Styling: TailwindCSS v4 (
@tailwindcss/vite),tailwind-merge,cva - UI: Custom components (
src/renderer/components/ui), Shadcn (based onreka-ui),lucide-vue-nexticons - State: Vue Composables (No Vuex/Pinia)
- Backend: Electron (Main),
better-sqlite3(DB), Elysia.js (API) - Utilities:
@vueuse/core,vue-sonner(Notifications)
YAGNI — simplicity above all. Do not overcomplicate code for hypothetical future scenarios. The minimum viable implementation is the correct implementation. Three similar lines of code are better than a premature abstraction.
Signs of overengineering:
- A function guards against a case that will never happen
- A factory used in exactly one place that doesn't encapsulate state
- Abstraction for its own sake (a wrapper around a single line of code)
- Constants or patterns invented in advance without a real need
Strict Separation of Concerns:
| Layer | Process | Access | Communication |
|---|---|---|---|
| Renderer | Frontend | NO Node.js/DB access. Only via API/IPC. | Calls API (api.*) or IPC (ipc.invoke) |
| API | Main | Full DB/System access. | Receives requests from Renderer |
| Main | Backend | Full System access. | Handles IPC & Lifecycle |
Data Flow: Renderer → REST API (Elysia) → Service/DB Layer → Response
| Type | Convention | Example |
|---|---|---|
| Vue components | PascalCase | Folders.vue, CreateDialog.vue |
| TypeScript files | camelCase | useSnippets.ts, errorMessage.ts |
Composables get a use prefix. The file name matches the exported function name: useSnippets.ts → export function useSnippets().
❌ DO NOT IMPORT:
- Vue core (
ref,computed,watch,onMounted) → Auto-imported - Project components (
src/renderer/components/) → Auto-imported (e.g.,<SidebarFolders />forcomponents/sidebar/Folders.vue)
✅ ALWAYS IMPORT MANUALLY:
- Shadcn UI:
import * as Select from '@/components/ui/shadcn/select' - Composables:
import { useApp } from '@/composables' - Utils:
import { cn } from '@/utils' - VueUse:
import { useClipboard } from '@vueuse/core' - Electron IPC/Store:
import { ipc, store } from '@/electron'
- Global State: Composables in
@/composables(e.g.,useApp,useSnippets) maintain shared state by defining reactive variables outside the exported function (module level). This ensures all components access the same state. No Pinia/Vuex. - Persistent Settings: Use
storefrom '@/electron'.store.app: UI state (sizes, visibility)store.preferences: User prefs (theme, language)
- Renderer: NEVER import
better-sqlite3. Useimport { api } from '~/renderer/services/api' - New Endpoints:
- Define DTO in
src/main/api/dto/ - Add route in
src/main/api/routes/ - Run
pnpm api:generateto update client.
- Define DTO in
- File System/System Ops: Use
ipc.invoke('channel:action', data). - Channels:
fs:*,system:*,db:*,main-menu:*,prettier:*,spaces:*. - Renderer: Access Electron only via
src/renderer/electron.ts.
massCode uses a Spaces system to organize different functional areas:
| Space | ID | Description |
|---|---|---|
| Code | code |
Main snippet management (folders, snippets, tags) |
| Tools | tools |
Developer utilities (converters, generators) |
| Math | math |
Math Notebook with calculation sheets |
Space Definitions: src/renderer/spaceDefinitions.ts — SpaceId, getSpaceDefinitions(), getActiveSpaceId().
Space State Persistence (Markdown Engine):
- Each space can store its state in
__spaces__/{spaceId}/.state.yamlinside the vault. - Runtime utilities:
src/main/storage/providers/markdown/runtime/spaces.ts—ensureSpaceDirectory(),getSpaceStatePath(). - Generic YAML read/write:
src/main/storage/providers/markdown/runtime/spaceState.ts—readSpaceState<T>(),writeSpaceState(). - Space state writes use the same debounce/flush infrastructure as
state.json(pendingStateWriteByPathinconstants.ts), so they flush automatically on app exit. __spaces__/directory exists only in markdown engine. When engine issqlite, spaces fall back toelectron-store.
Space IPC Channels:
spaces:math:read— read Math Notebook state (auto-migrates from electron-store on first read in markdown mode).spaces:math:write— persist Math Notebook state.- Handlers:
src/main/ipc/handlers/spaces.ts.
Space-Aware Sync:
system:storage-syncedevent dispatches refresh based ongetActiveSpaceId():code/null→ refresh folders + snippetsmath→reloadFromDisk()viauseMathNotebook()tools→ no-op (no vault data)
- Mutable operations must call
markPersistedStorageMutation()to prevent sync loops.
- Primary Language: English (EN) is the base language. All new keys MUST be added to
src/main/i18n/locales/en_US/first. - Strictly No Hardcoding: Never use hardcoded strings in templates or logic. Always use the localization system.
- Usage: Use
i18n.t('namespace:key.path')in both templates and scripts. - Default Namespace: The
uinamespace is the default. You can usei18n.t('key.path')instead ofi18n.t('ui:key.path'). - Imports:
import { i18n } from '@/electron'
- Variants: Use
cvafor component variants. - Classes: Use
cn()to merge Tailwind classes. - Notifications: Use
useSonner()composable. - Utilities / Composables:
- Check
@vueuse/corefirst. Most common logic (clipboard, events, sensors) is already implemented. - Only create custom composables if no suitable VueUse utility exists.
- Remember to manually import them (e.g.,
import { useClipboard } from '@vueuse/core').
- Check
- Component Usage (STRICT):
- NEVER reimplement basic UI elements (buttons, inputs, checkboxes, etc.).
- ALWAYS use existing components from
src/renderer/components/ui/. - Missing Elements: If a required UI element does not exist, create it in
src/renderer/components/ui/first, following established patterns (Tailwind, cva, cn), then use it. - Naming: They are auto-imported with a
Uiprefix (e.g.,<UiButton />,<UiInput />,<UiCheckbox />).
Split a component when it exceeds ~300 lines or has more than 3 unrelated responsibilities:
- Extract constants and static data
- Extract pure functions into utils (only if used in multiple places)
- Move state and effects into a composable
- Break the template into child components
Keep no logic in <template> more complex than a ternary operator.
Linting (CRITICAL):
- ALWAYS scope lint commands to specific files/dirs.
- NEVER run lint on the whole project during a task.
- Usage:
pnpm lint <path>orpnpm lint:fix <path>
Other Commands:
pnpm dev: Start dev serverpnpm api:generate: Regenerate API client (required after API changes)pnpm build: Build for production
Component Setup:
<script setup lang="ts">
import * as Dialog from '@/components/ui/shadcn/dialog' // Manual import
import { useSnippets } from '@/composables' // Manual import
const { snippets } = useSnippets()
// ref, computed are auto-imported (Vue core)
</script>
<template>
<div>
<!-- Use auto-imported UI component -->
<UiButton>Click me</UiButton>
<!-- Use Shadcn components with namespace -->
<Dialog.Dialog>
<Dialog.DialogTrigger as-child>
<UiButton variant="outline">Open</UiButton>
</Dialog.DialogTrigger>
<Dialog.DialogContent>
Snippet count: {{ snippets.length }}
</Dialog.DialogContent>
</Dialog.Dialog>
</div>
</template>Data Fetching (Renderer):
import { api } from '~/renderer/services/api'
const { data } = await api.snippets.getSnippets({ folderId: 1 })IPC Call:
import { ipc } from '@/electron'
await ipc.invoke('fs:assets', { buffer, fileName })Localization:
<script setup lang="ts">
import { i18n } from '@/electron'
</script>
<template>
<div>
<!-- Using default 'ui' namespace -->
<p>{{ i18n.t('common.save') }}</p>
<!-- Using specific namespace -->
<p>{{ i18n.t('messages:snippets.count', { count: 10 }) }}</p>
</div>
</template>Creating New API Endpoint (DTO & Route):
-
Define DTO (
src/main/api/dto/snippets.ts):import { t } from 'elysia' // Define validation schema const snippetsDuplicate = t.Object({ id: t.Number() }) // Register in main DTO model export const snippetsDTO = new Elysia().model({ // ... other DTOs snippetsDuplicate })
-
Add Route (
src/main/api/routes/snippets.ts):import { useDB } from '../../db' app.post('/duplicate', ({ body }) => { const db = useDB() // Database logic here... return { id: newId } }, { body: 'snippetsDuplicate', // Use registered DTO name detail: { tags: ['Snippets'] } })
-
Generate Client: Run
pnpm api:generate