This project is using Vite+, a unified toolchain built on top of Vite, Rolldown, Vitest, tsdown, Oxlint, Oxfmt, and Vite Task. Vite+ wraps runtime management, package management, and frontend tooling in a single global CLI called vp. Vite+ is distinct from Vite, but it invokes Vite through vp dev and vp build.
vp is a global binary that handles the full development lifecycle. Run vp help to print a list of commands and vp <command> --help for information about a specific command.
- create - Create a new project from a template
- migrate - Migrate an existing project to Vite+
- config - Configure hooks and agent integration
- staged - Run linters on staged files
- install (
i) - Install dependencies - env - Manage Node.js versions
- dev - Run the development server
- check - Run format, lint, and TypeScript type checks
- lint - Lint code
- fmt - Format code
- test - Run tests
- run - Run monorepo tasks
- exec - Execute a command from local
node_modules/.bin - dlx - Execute a package binary without installing it as a dependency
- cache - Manage the task cache
- build - Build for production
- pack - Build libraries
- preview - Preview production build
Vite+ automatically detects and wraps the underlying package manager such as pnpm, npm, or Yarn through the packageManager field in package.json or package manager-specific lockfiles.
- add - Add packages to dependencies
- remove (
rm,un,uninstall) - Remove packages from dependencies - update (
up) - Update packages to latest versions - dedupe - Deduplicate dependencies
- outdated - Check for outdated packages
- list (
ls) - List installed packages - why (
explain) - Show why a package is installed - info (
view,show) - View package information from the registry - link (
ln) / unlink - Manage local package links - pm - Forward a command to the package manager
- upgrade - Update
vpitself to the latest version
These commands map to their corresponding tools. For example, vp dev --port 3000 runs Vite's dev server and works the same as Vite. vp test runs JavaScript tests through the bundled Vitest. The version of all tools can be checked using vp --version. This is useful when researching documentation, features, and bugs.
- Using the package manager directly: Do not use pnpm, npm, or Yarn directly. Vite+ can handle all package manager operations.
- Always use Vite commands to run tools: Don't attempt to run
vp vitestorvp oxlint. They do not exist. Usevp testandvp lintinstead. - Running scripts: Vite+ built-in commands (
vp dev,vp build,vp test, etc.) always run the Vite+ built-in tool, not anypackage.jsonscript of the same name. To run a custom script that shares a name with a built-in command, usevp run <script>. For example, if you have a customdevscript that runs multiple services concurrently, run it withvp run dev, notvp dev(which always starts Vite's dev server). - Do not install Vitest, Oxlint, Oxfmt, or tsdown directly: Vite+ wraps these tools. They must not be installed directly. You cannot upgrade these tools by installing their latest versions. Always use Vite+ commands.
- Use Vite+ wrappers for one-off binaries: Use
vp dlxinstead of package-manager-specificdlx/npxcommands. - Import JavaScript modules from
vite-plus: Instead of importing fromviteorvitest, all modules should be imported from the project'svite-plusdependency. For example,import { defineConfig } from 'vite-plus';orimport { expect, test, vi } from 'vite-plus/test';. You must not installvitestto import test utilities. - Type-Aware Linting: There is no need to install
oxlint-tsgolint,vp lint --type-awareworks out of the box.
- Run
vp installafter pulling remote changes and before getting started. - Run
vp checkandvp testto validate changes.
Document organization web app for managing tax documents, records, and files across multiple entities.
DocVault is an open-source, public repository. It is used by the maintainer against a private NAS containing real tax, financial, medical, and identity records. Personal data MUST NEVER be committed.
Do not commit any of the following into the public repo, regardless of whether it "looks like test data":
- Real names (the user's, family members, clients, employers, business partners)
- Real account numbers / IDs (bank account last-4, credit card last-4, SimpleFIN IDs, SSN fragments, EINs, routing numbers, loan numbers)
- Real dollar amounts tied to the user's household (income, balances, mortgage amounts, specific transaction totals)
- Real addresses, phone numbers, emails — none, including the maintainer's
- Real vendor/payer names from invoices or 1099s
- Real health/medical data (Apple Health exports, diagnoses, dates of visits)
Where this data may legitimately live:
data/directory — gitignored (data/,data-backup)- Test files (
**/*.test.ts,**/*.test.tsx,**/*.spec.*) — gitignored with exception forserver/routes/quant.test.ts(pure math, no personal data) tax-plan/,scripts/,server/parsers/fixtures/— gitignored.docvault-*.jsonfiles — gitignored as part ofdata/
When writing code or tests that need realistic fixtures: fabricate obviously-fake data (Acme Bank, $1,234.56, John Doe). If you must use real data to verify, put it in a gitignored test file (*.test.ts) — the .gitignore already handles the whole pattern.
Before every commit, run git status and git diff --cached and visually scan for any of the categories above. If in doubt, move the content to a gitignored location.
When adding a new file that might contain personal data, either:
- Place it under an already-gitignored path, OR
- Extend
.gitignorein the same commit.
- Frontend: Vite + React + TypeScript + Tailwind CSS
- Backend: Bun native server (
Bun.serve(), not Express) - Storage: Local filesystem (data dir, volume-mounted in Docker)
- Infrastructure: Docker → GHCR, GitHub Actions CI
bun start # Frontend (5173) + backend (3005) with hot-reload
bun run server # Backend only
bun run dev # Frontend onlyDo NOT start the dev server — the user manages it manually.
NAS data: Always SSH to NAS (ssh nas) and read from /mnt/user/appdata/docvault/data/ for real data. Local data/ symlinks may be stale.
CRITICAL — NAS file edits: When modifying JSON files on the NAS, NEVER pipe output back to the same file being read (e.g., cat file | jq ... | cat > file — this truncates the file to 0 bytes before reading finishes). Always:
- Read the file into a variable or temp file first
- Write the modified content to a NEW temp file
- Move/copy the temp file over the original
- Or use
node -eon the NAS to read, modify, and write in one process
Example safe pattern:
ssh nas 'node -e "const fs=require(\"fs\"); const d=JSON.parse(fs.readFileSync(\"/path/to/file.json\",\"utf8\")); /* modify d */ fs.writeFileSync(\"/path/to/file.json\",JSON.stringify(d,null,2));"'Sidebar navigation: When adding a new view to the sidebar (Sidebar.tsx → NavButton), you MUST also:
- Add the view name to the
NavViewunion type insrc/contexts/AppContext.tsx - Add the view name to the
validViewsSet insrc/contexts/AppContext.tsx(around line 221) - Add the
case 'your-view':to the view switch insrc/components/Layout/Layout.tsx
Missing any of these causes the sidebar click to reload the page instead of navigating.
Package installs: This project uses both pnpm and bun. After adding a dependency with pnpm add <pkg>, always run bun install to sync bun.lock, then commit both package.json and bun.lock. The Docker build uses bun install --frozen-lockfile so an out-of-sync bun.lock breaks CI. pnpm-lock.yaml is gitignored.
All state lives in DATA_DIR (default ./data, /data in Docker) as .docvault-*.json files:
| File | Purpose |
|---|---|
.docvault-config.json |
Entity definitions (names, types, metadata) |
.docvault-settings.json |
API keys, exchange secrets, sync config |
.docvault-parsed.json |
Cached AI parse results |
.docvault-reminders.json |
Deadline reminders |
.docvault-metadata.json |
Document tags/notes |
.docvault-sync-status.json |
Dropbox sync status (written by NAS cron) |
Entity types: tax (year-based views with income/expenses) or docs (flat file listing).
| File | Role |
|---|---|
server/index.ts |
All backend logic |
src/contexts/AppContext.tsx |
Central state (entity, view, year, docs) |
src/hooks/useFileSystemServer.ts |
Frontend API client |
src/config.ts |
Document types, expense categories |
src/utils/filenaming.ts |
Auto-naming ({Source}_{Type}_{Date}.ext) |
All endpoints prefixed /api/. Entity-aware. Key routes:
GET /entities— list entitiesGET /files/:entity/:year— files for entity/yearPOST /upload?entity=X&path=Y&filename=Z— uploadPOST /parse/:entity/:path— AI parse single filePOST /parse-all/:entity/:year— batch parseGET /tax-summary/:year— consolidated tax dataPOST /backup/POST /restore— encrypted backup/restore
- Multi-stage: Vite build →
oven/bun:1-slimruntime - Single volume mount: data dir →
/data - Image:
ghcr.io/vanities/docvault:latest - Auto-publishes on push to main (amd64 + arm64)