Before searching the codebase, read docs/START-HERE.md first — it provides the full bootstrap sequence.
If the hard part of the task is following behavior end-to-end rather than understanding subsystem design, also read docs/flows/README.md.
If work touches AI import/export, also read docs/architecture/ai-import-export-architecture.md.
Phase 4 planning is documented in docs/plan/phase-4-detailed-plan.md. The recommended execution sequence is: WS1 (Wiki Tag Links) → WS2 (Folder Operations) → WS3 (Templates) → WS4 (Corkboard) → WS5 (AI Import/Export) → Phase 4 closure.
For WS1 execution details, see docs/spec/wiki-tag-links-spec.md and docs/plan/done/wiki-tag-links-implementation-plan.md.
npm run dev- Starts Vite + Electron desktop flow.
npm run build- Builds renderer and Electron outputs.
npm run lint- Runs ESLint with
--max-warnings 0.
- Runs ESLint with
npm run test- Runs full Vitest suite.
- Note: In sandboxed agent environments (e.g., Qwen Code),
npm testmay fail due to environment restrictions. Use the PowerShell script instead:powershell -ExecutionPolicy Bypass -File scripts/run-tests.ps1- VS Code shortcut:
Ctrl+Shift+T(task: "Run Tests & Report") - Report saved to
reports/test-report.txtwith timestamp.
- Focused test runs work normally:
npm run test -- tests/sidebar-panels.test.ts
npm run test:smoke- Builds app and runs Electron startup smoke test.
npm run dev:renderer- Start Vite only.
npm run dev:electron- Build Electron layer and launch against local Vite URL.
npm run build:electron- Compile only Electron TypeScript layer.
- Focused test runs (use PowerShell script or
npm run test --):npm run test -- tests/sidebar-panels.test.tsnpm run test -- tests/use-project-editor.test.tsnpm run test -- tests/ipc-contract.test.tsnpm run test -- tests/rich-markdown-editor.test.tsnpm run test -- tests/project-editor-conflict-flow.test.tsnpm run test -- tests/typescript-compile.test.ts
- Start with
npm run dev. - In app, pick folder and verify preload status is available.
- Validate core flow for touched area (editor/sidebar/IPC).
- If the behavior path is hard to follow, open the matching doc in
docs/flows/before changing code. - Run
npm run lintand focused tests while iterating. - Run tests with
npm run testbefore finishing. - Run
npm run buildfor final compile confidence. - Run
npm run test:smokewhen touching preload/window/IPC startup paths. - Update the documentation (see mandatory checklist below)
- If a whole md implementation plan is finished, move it to
docs/plan/done.
When a change affects behavior (not only formatting) or when detecting anything in the code that contradicts existing documentation, documentation updates are required in the same task. Read the requirements in docs/update.md.
Use the doc types consistently:
docs/architecture/for subsystem design and invariantsdocs/flows/for "when this happens, what runs next?"docs/lessons-learned/for counter-intuitive factsdocs/plan/for active refactors or implementation slices
Add or update a flow doc when:
- the debugging path is easier to explain as a trigger sequence than as architecture
- you had to trace several files to answer "what happens when I do X?"
- a behavior hotspot keeps forcing repeated codebase searches
Good candidates:
- typing in the editor
- switching file
- switching pane
- saving
- external file change handling
- window close flow
- Filter from sidebar input manually.
- Collapse all folders and confirm state remains collapsed.
- Create article/category from footer.
- Right-click file row and validate rename/delete flow.
- Confirm newly created empty folders are visible in tree.
- Focus editor and press Ctrl/Cmd+F to open floating find bar.
- Type query and verify active match highlight appears in the document.
- Press Enter / Shift+Enter and verify next/previous navigation updates counter and highlight.
- Verify typing keeps focus in find input (no focus steal to editor).
- In split mode, open one file in primary and another in secondary.
- Type in secondary and verify only secondary dirty indicator/status changes.
- Switch active pane and verify dirty state remains attached to the edited pane.
- Run
npm run test -- tests/project-editor-conflict-flow.test.tsafter touching split-pane editor/state wiring.
- Add channel + schemas in
src/shared/ipc.ts. - Implement handler in
electron/ipc/handlers/.... - Register channel in
electron/ipc.ts. - Expose preload API in
electron/preload.cts. - Update typings in
src/types/trama-api.d.ts. - Update renderer action hooks/components.
- Add/update tests.
- When adding workspace context-menu commands, update
src/shared/workspace-context-menu.ts, add the native menu entry inelectron/main-process/context-menu.ts, and handle the command in the renderer (use-project-editor-context-menu-effect.tsand any editor listeners such asrich-markdown-editor-core.ts). Add focused tests for the command.
Every time you make a change to fix a bug, leave logs along the way to double check your assumptions.
After the user confirms you that the bug is fixed, delete the logs.
Before writing any code that interacts with Quill's API (coordinates, bounds, selection, events, DOM structure), consult the official docs first:
- API reference: https://quilljs.com/docs/api
- Key gotcha:
quill.getBounds()returns coordinates relative toquill.container, notquill.root. Seedocs/lessons-learned/quill-getbounds-container-reference.md. - Quill owns its DOM (
quill.root/.ql-editor). Never inject or mutate nodes inside it; overlays must be siblings outside.ql-editor. - Embedded markdown images must use a single canonical in-memory representation while editing. In Trama, editor state should keep short
<!-- IMAGE_PLACEHOLDER:... -->markers plus the cached image map, and only hydrate back towhen rendering into Quill or saving to disk. - When syncing external editor values, compare canonicalized markdown, not raw source text. Base64 markdown and placeholder markdown may represent the same document; treating them as different will trigger destructive re-renders and can drop typed text or images.
- If the issue is behavioral rather than API-specific, check
docs/flows/rich-editor-typing-flow.mdbefore changing editor lifecycle code.
- The user doesn't want you to spend long time reasoning, consider adding logs or fetching the documentation of external libraries that are causing issues.
max-lines: 200andmax-lines-per-function: 50are enforced.- Split UI into helper components/hooks before limits are hit.
- Keep
electron/ipc.tsthin; move logic into handlers/services. - lint limits are meant to encourage to break down long files and long functions, and avoid code repetition, NOT to compact white spaces/indentation.
Every useEffect, useCallback, useMemo, and similar hook must include a descriptive name comment and dependency documentation:
🤢🤮❌ BAD, TERRIBLE, AWFUL, ABOMINATION
useEffect(() => { ... }, [dep1, dep2])
const fn = useCallback(() => { ... }, [dep1, dep2])
const value = useMemo(() => { ... }, [dep1, dep2])GOOD
useEffect(/* descriptiveEffectName */ () => { ... }, [dep1, dep2] /*Inputs for descriptiveEffectName*/)
const fn = useCallback(/* descriptiveFnName */ () => { ... }, [dep1, dep2] /*Inputs for descriptiveFnName*/)
const value = useMemo(/* descriptiveMemoName */ () => { ... }, [dep1, dep2] /*Inputs for descriptiveMemoName*/)When there are no dependencies, use /*Inputs for name — stable*/ to make the intent explicit.
npm run devusesconcurrently --kill-others --success command-electron.- If Electron exits, renderer process will also terminate by design.
After npm run build:electron, verify:
dist-electron/electron/main.jsdist-electron/electron/ipc.jsdist-electron/electron/preload.cjsdist-electron/electron/ipc/handlers/index.jsdist-electron/src/shared/ipc.js
If preload.cjs is missing or main points to another path, window.tramaApi will be unavailable.