Skip to content
CelPlume
select · Enteropen · Escclose Powered by Pagefind

Development guide

Local setup, scripts, code conventions, testing, and commit rules.

This guide covers contributing to BookmarkHarbor: environment setup, scripts, code conventions, testing expectations, and the commit convention. It assumes you have a working clone of the repository. The application is a single front end, so all work happens in one package rooted at the repository.

ToolVersionPurpose
Node.js20.19 or later, or 22.12 or laterJavaScript runtime for build tooling (Vite 8 requirement)
bun1.2 or laterPackage manager and task runner

The project uses bun as its single package manager (packageManager: bun@1.3.14). Do not mix npm, pnpm, or yarn lockfiles into the repository. Installing with npm produces an incompatible lockfile and is not supported (npm 12 is incompatible with the Node 22 runtime in use).

  1. Install the dependencies.

    bun install
  2. Start the development server.

    bun run dev

The Vite dev server listens on http://localhost:3000 and opens your browser automatically. There is no backend or database to configure.

TaskCommandNotes
Test suitebun run testRuns Vitest once in jsdom.
Test watchbun run test:watchRe-runs on change.
Test coveragebun run test:coverageGenerates a coverage report.
Type checkbun run lintRuns tsc --noEmit.
Buildbun run buildRuns tsc -b && vite build, emitting to dist/.
Previewbun run previewServes the dist/ build locally.

The test suite runs in jsdom (see vitest.config.ts) with src/test/setup.ts providing localStorage and crypto.randomUUID mocks.

Follow these conventions so the codebase stays consistent.

  • Files: kebab-case
  • React components: PascalCase
  • Functions and variables: camelCase
  • Constants: UPPER_SNAKE_CASE or PascalCase for object references
  • Types and interfaces: PascalCase
  • Keep strict mode enabled (tsconfig.json sets strict: true).
  • Avoid any; if you must use it, add a comment that explains why.
  • The tsconfig.json enables noUnusedLocals and noUnusedParameters, so every commit must compile cleanly.
  • Run bun run lint after you change types.

Order imports as follows: external libraries first, then framework modules, then project-internal modules (./core, ./components, ./i18n), with type-only imports last where convenient.

  • Framework-free domain logic goes in src/core/ so it can be unit-tested without a DOM.
  • React UI goes in src/components/ using HeroUI (React Aria) compound components, Iconify for icons, and Tailwind CSS for styling.
  • Do not introduce a second component library or icon set; the project standardizes on HeroUI and Iconify.
  • Prefer composition over inheritance, and keep component granularity moderate. Do not push all logic into a single page component.
  • Follow the existing visual baseline (colors, spacing, shadows, and elevation) rather than inventing a new look.
  • Any component change must remain usable in both light and dark themes.
  • Keep keyboard and mouse selection semantics consistent across views (single, multi, range, rename, drag). Do not ship an interaction that “looks usable” but applies different rules.
  • All user-facing strings come from src/i18n/translations/ via useTranslation. Add keys to both zh.ts and en.ts. en.ts uses the Translation type as its contract, so a missing key fails the type check.

Add tests in src/test/ when you change a src/core/ module. The existing suite uses Vitest:

FileWhat it locks in
cycleDetection.test.tsCycle detection refuses moving a folder into itself or a descendant; descendant and ancestor traversal.
orderKey.test.tsOrder keys sort correctly and insert between neighbors (midpoint, increment, decrement, bulk runs).
htmlParser.test.tsParses Netscape bookmark HTML into folders, bookmarks, tags, notes, and URLs.

Cover the following when you change related code:

  • Cycle detection: a folder must never be its own ancestor. Add cases that walk multi-level trees.
  • Ordering: inserting between any two neighbors must yield a key that sorts in place without rewriting siblings; bulk key generation must stay monotonic.
  • Import parsing: malformed or partial HTML must not throw; folders and bookmarks, attributes, tags, and nested notes must round-trip.

Run bun run test after changes. Do not add dependencies to make tests pass; mock browser APIs through src/test/setup.ts instead.

Use Conventional Commits: type(scope): subject. When the change has a meaningful body, prefer multiple -m flags, each flag one bullet point.

git commit -m "feat(ui): add draggable sidebar and inspector panel resizers" \
-m "- Add a PanelResizer handle using pointer capture." \
-m "- Persist panel widths to aurabookmarks_panel_widths."

Use these types and scopes.

TypeMeaning
featNew feature
fixBug fix
docsDocumentation only
styleFormatting, no behavior change
refactorCode change with no behavior change
perfPerformance improvement
testTest additions or changes
choreMaintenance
ScopeArea
i18nTranslation modules and keys
settingsSettings modal and default-view behavior
uiComponents and layout
file-browserBookmark list, cards, selection toolbar
sidebarSidebar navigation and branding
inspectorProperty panel
headerTop bar and search

Keep each commit focused on one module. If a change spans modules, split it; this keeps history reviewable and matches the existing commit history.

Review each change against this checklist before you finish.

  • The working tree compiles: bun run lint passes.
  • The build succeeds: bun run build passes.
  • Tests pass: bun run test passes. Add a test that fails on a plausible regression when you change src/core/.
  • No dead code, leftover debug logging, or commented-out blocks.
  • New user-facing strings exist in both zh.ts and en.ts.
  • Verify UI changes against the actual surface (run the app or a component check) and keep them consistent in light and dark themes.
  • Update documentation references when you change structure, settings, or conventions.

git add the whole file is fine; staging single hunks is brittle

Section titled “git add the whole file is fine; staging single hunks is brittle”

The repository keeps noUnusedLocals and noUnusedParameters on. If you split a file’s changes across commits, each intermediate state must compile on its own. Prefer committing a whole module at a time, or keep dependent imports and usages in the same commit.

Do not rename aurabookmarks_data or aurabookmarks_panel_widths without a migration. loadFromStorage already normalizes older data (for example, mapping legacy grid view to card and clamping column counts), so preserve that path when you evolve the schema.

Do not add a package unless the existing stack cannot do the job. The project standardizes on HeroUI, Iconify, Tailwind, Zod, i18next, and @dnd-kit. New UI or icon libraries are a rejected pattern; check Context7 documentation before using an unfamiliar API.

lint runs tsc --noEmit with strict options. Because noUnusedLocals is on, an unused import or variable fails the check and blocks the build.