diff --git a/.claude/settings.json b/.claude/settings.json index 5c49ccbf..8840b142 100644 --- a/.claude/settings.json +++ b/.claude/settings.json @@ -1,5 +1,4 @@ { - "model": "opusplan", "env": { "CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS": "1" }, @@ -61,6 +60,7 @@ ], "defaultMode": "auto" }, + "model": "opusplan", "hooks": { "Notification": [ { @@ -89,7 +89,8 @@ "frontend-design@claude-plugins-official": true, "security-guidance@claude-plugins-official": true, "superpowers@claude-plugins-official": true, - "typescript-lsp@claude-plugins-official": true + "typescript-lsp@claude-plugins-official": true, + "understand-anything@understand-anything": true }, "sandbox": { "enabled": true, diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 5fc1ff66..b67595c7 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -2,6 +2,8 @@ **Scope:** Repo-wide guardrails and navigation for GitHub Copilot Chat. Keep changes within this monorepo; respect existing workflows and Claude skills. +Keep `CLAUDE.md` in sync: mirror cross-cutting rule changes back to `CLAUDE.md`. + ## How to Use This Guide - Start here, then jump to the focused partials in `.github/agents/` for on-demand detail. - For automation recipes, see `.github/agents/skills-index.md` to leverage existing `.claude/skills` instead of duplicating them. @@ -37,6 +39,9 @@ When responding to PR review feedback, do not directly apply reviewer suggestions to files in `.agents/skills/` — post a reply noting the suggestion will be addressed upstream instead. Skills sourced from `WhatIfWeDigDeeper/agent-skills` (e.g., `pr-comments`, `ship-it`, `learn`, `playwright-cli`) are maintained upstream; deliberate version upgrades or syncs via dedicated PRs are fine. Only project-owned files (`scripts/`, `.vscode/`, `docs/`, `fastapi/`, application source) are in-scope for directly applying reviewer feedback. ## Cross-Cutting Patterns +- **Bash write loops silently fail in sandbox**: `for f in ...; do cp ...; done` loops writing to project subdirs silently no-op — use `python3 -c "import shutil; shutil.copy2(src, dst)"` instead. +- **gitignore slash anchors pattern to root**: A pattern with an internal `/` (like `foo/bar/`) is anchored to the `.gitignore` root — use `**/foo/bar/` to match nested directories too. +- **README TOC**: When adding a section or subsection to `README.md`, add a matching TOC entry. Anchor format: lowercase, spaces → `-`, drop special characters except hyphens. Subsections indent two spaces under their parent. - **Validation limit changes**: When updating max lengths in constants/schemas, grep for hardcoded boundary values in tests (e.g., `repeat(1001)`) — tests may silently pass with stale limits - **GitHub CLI pager fallback in VS Code**: If `gh` opens the alternate buffer or exits 130 despite `GH_PAGER=cat PAGER=cat`, redirect output to a temp file and inspect it in the editor or with CLI tools like `cat`, `sed`, or `rg` (for example, `TMP=$(mktemp ...); gh pr view ... > "$TMP"`). - **Shared tests**: See `tests/CLAUDE.md` for API/E2E lifecycle, `--runInBand`, cleanup, and Playwright/WebKit quirks. diff --git a/.gitignore b/.gitignore index fff97c7a..db321251 100644 --- a/.gitignore +++ b/.gitignore @@ -207,3 +207,7 @@ data/ # AWS CDK synthesis output cdk.out/ + +# understand-anything build artifacts +**/.understand-anything/intermediate/ +**/.understand-anything/tmp/ diff --git a/.understand-anything/.understandignore b/.understand-anything/.understandignore new file mode 100644 index 00000000..a26b533d --- /dev/null +++ b/.understand-anything/.understandignore @@ -0,0 +1,111 @@ +# .understandignore — patterns for files/dirs to exclude from analysis +# Syntax: same as .gitignore (globs, # comments, ! negation, trailing / for dirs) +# Lines below are suggestions — uncomment to activate. +# Use ! prefix to force-include something excluded by defaults. +# +# Built-in defaults (always excluded unless negated): +# node_modules/, .git/, dist/, build/, obj/, *.lock, *.min.js, etc. +# +# --- From .gitignore (uncomment to exclude) --- + +# logs +# npm-debug.log* +# yarn-debug.log* +# yarn-error.log* +# lerna-debug.log* +# report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json +# pids +# *.pid +# *.seed +# *.pid.lock +# lib-cov +# *.lcov +# .nyc_output +# .grunt +# bower_components +# .lock-wscript +# build/Release +# jspm_packages/ +# web_modules/ +# *.tsbuildinfo +# .npm +# .eslintcache +# .stylelintcache +# .node_repl_history +# *.tgz +# .yarn-integrity +# .env +# .env.* +# !.env.example +# .parcel-cache +# next-env.d.ts +# .nuxt +# .output +# .vuepress/dist +# .temp +# .svelte-kit/ +# **/.vitepress/dist +# **/.vitepress/cache +# .docusaurus +# .serverless/ +# .fusebox/ +# .dynamodb/ +# .firebase/ +# .tern-port +# .vscode-test +# .pnp.* +# .yarn/* +# !.yarn/patches +# !.yarn/plugins +# !.yarn/releases +# !.yarn/sdks +# !.yarn/versions +# vite.config.js.timestamp-* +# vite.config.ts.timestamp-* +# **/vite.config.js +# **/vite.config.d.ts +# !vite.config.ts +# test-results/ +# playwright-report* +# playwright/.cache/ +# *.swp +# *.swo +# *~ +# .DS_Store +# Thumbs.db +# .vercel +# *.cpuprofile +# *.heapprofile +# .claude/settings.local.json +# .claude/scheduled_tasks.lock +# .agent/skills/ +# .agents/skills/ +# .claude/skills/js-deps +# .claude/skills/learn +# .claude/skills/mermaid-diagrams +# .claude/skills/peer-review +# .claude/skills/playwright-cli +# .claude/skills/pr-comments +# .claude/skills/pr-human-guide +# .claude/skills/ship-it +# .claude/skills/uv-deps +# .claude/skills/vercel-react-best-practices +# .playwright-cli +# scripts/.tmp/ +# *.py[cod] +# *.egg-info/ +# rails-api/tmp/ +# data/ +# cdk.out/ + +# --- Detected directories (uncomment to exclude) --- + +# tests/ +# docs/ +# scripts/ + +# --- Test file patterns (uncomment to exclude) --- + +# *.test.* +# *.spec.* +# *.snap diff --git a/.understand-anything/domain-graph.json b/.understand-anything/domain-graph.json new file mode 100644 index 00000000..2e1113f1 --- /dev/null +++ b/.understand-anything/domain-graph.json @@ -0,0 +1,856 @@ +{ + "version": "1.0.0", + "project": { + "name": "application-tracker", + "languages": ["TypeScript", "JavaScript", "Python", "Go", "Java", "Ruby"], + "frameworks": ["Angular", "React", "Vue", "Svelte", "Next.js", "TanStack", "FastAPI", "Express", "Hono", "Koa", "NestJS", "Gin", "Spring Boot", "Rails", "GraphQL Yoga"], + "description": "A monorepo job application tracker with 20+ full-stack implementations sharing one PostgreSQL database. Core business purpose: track job applications through the hiring pipeline, manage interview stages, and audit changes over time.", + "analyzedAt": "2026-05-05T00:00:00.000Z", + "gitCommitHash": "e5054096efec8165e7d4cfe11fc103f77ad2d98f" + }, + "nodes": [ + { + "id": "domain:application-management", + "type": "domain", + "name": "Application Management", + "summary": "The core business domain covering the full lifecycle of a job application record. Handles creation, retrieval, update, deletion, and soft archiving/restoring of applications with rich metadata including company info, salary range, job source, and status progression.", + "tags": ["applications", "crud", "core", "lifecycle", "archive"], + "complexity": "complex", + "domainMeta": { + "entities": ["Application", "ApplicationStatus", "CompanyCategory", "JobSource"], + "businessRules": [ + "Applications default to 'unsubmitted' status when no dateApplied is provided", + "Archived applications are soft-deleted (isArchived flag) and can be restored", + "All applications are sorted by updatedAt descending by default", + "Duplicate detection uses job posting URL or (companyName, positionTitle) pair" + ], + "crossDomainInteractions": [ + "Triggers history snapshots on every mutating operation (History Tracking domain)", + "Owns the parent record for all interview stages (Interview Stage domain)", + "Provides application data for CSV import/export (Data Portability domain)" + ] + } + }, + { + "id": "domain:interview-stage-management", + "type": "domain", + "name": "Interview Stage Management", + "summary": "Manages the ordered pipeline of interview stages within a job application. Each stage tracks the type of interview, scheduling details, outcome, and completion status. Stage CRUD operations always touch the parent application's updatedAt timestamp to keep the list sort order accurate.", + "tags": ["interview-stages", "pipeline", "crud", "ordering"], + "complexity": "moderate", + "domainMeta": { + "entities": ["InterviewStage", "Application"], + "businessRules": [ + "A stage must belong to an existing application (verified before insert)", + "Deleting or updating a stage bumps the parent application's updatedAt", + "Stages are ordered by an explicit 'order' field managed by the client", + "Completion of a stage auto-records the completion date" + ], + "crossDomainInteractions": [ + "Every stage mutation records a history snapshot (History Tracking domain)", + "Stages are embedded in Application responses (Application Management domain)" + ] + } + }, + { + "id": "domain:history-tracking", + "type": "domain", + "name": "History Tracking", + "summary": "Maintains a full audit trail of changes to job applications. Implementations range from simple snapshot storage (most stacks), to event-sourcing with Immer patches (NestJS/Nuxt), to version restore from historical snapshots (Go, Lambda/DynamoDB). All stacks record who changed what, with field-level diffs viewable in the history panel.", + "tags": ["history", "audit", "snapshots", "versioning", "event-sourcing"], + "complexity": "complex", + "domainMeta": { + "entities": ["ApplicationHistory", "HistorySnapshot", "FieldDiff", "Event"], + "businessRules": [ + "A snapshot is taken automatically after every create, update, archive, restore, and stage mutation", + "History entries are returned newest-first with pagination", + "Version restore rebuilds application state from a target snapshot or event sequence", + "Event-sourced stacks replay forward patches from the nearest snapshot to the target sequence" + ], + "crossDomainInteractions": [ + "Receives snapshot triggers from Application Management and Interview Stage domains", + "NestJS stack exposes history via a separate gRPC microservice consumed by the UI" + ] + } + }, + { + "id": "domain:data-portability", + "type": "domain", + "name": "Data Portability", + "summary": "Enables bulk import and export of job application data via CSV files. Import parses and validates rows, deduplicates by job posting URL or compound key, and upserts applications. Export serializes the full application list to a downloadable CSV. A sample template endpoint helps users format their CSV correctly.", + "tags": ["csv", "import", "export", "bulk", "serialization"], + "complexity": "moderate", + "domainMeta": { + "entities": ["CsvRow", "ImportResult", "ApplicationCsv"], + "businessRules": [ + "Multi-line quoted CSV fields are supported (character-by-character parser, not line-split)", + "Duplicate rows are detected by jobPostingUrl, falling back to (companyName, positionTitle)", + "Each CSV row is individually validated; errors are reported per-row without aborting the import", + "Export fetches all applications via the paginated list endpoint in a loop" + ], + "crossDomainInteractions": [ + "Import invokes Application Management create/update flows for each valid row", + "Export consumes the Application Management list query" + ] + } + }, + { + "id": "domain:platform-infrastructure", + "type": "domain", + "name": "Platform Infrastructure", + "summary": "Cross-cutting infrastructure concerns shared across all stacks: database connectivity and schema migrations, health check endpoints for monitoring, CORS middleware, error handling, and cloud deployment (AWS Lambda + DynamoDB for the serverless stack, CDK for IaC). Provides the runtime foundation for all business domains.", + "tags": ["infrastructure", "database", "migrations", "health", "cors", "deployment"], + "complexity": "moderate", + "domainMeta": { + "entities": ["DatabasePool", "Migration", "HealthCheck", "CdkStack"], + "businessRules": [ + "All schemas use IF NOT EXISTS guards — the shared app_tracker database may have pre-existing state", + "Each stack isolates its data in a named PostgreSQL schema (e.g. express_prisma, go_gin, python_fastapi)", + "Health endpoints return {status: ok, timestamp} and are used by load balancers", + "DynamoDB stack uses GSI1/GSI2 for status/archived filtering without full table scans" + ], + "crossDomainInteractions": [ + "Provides the database connection pool consumed by all service layers", + "Migration runner executes before application startup in every stack" + ] + } + }, + { + "id": "flow:create-application", + "type": "flow", + "name": "Create Application", + "summary": "User submits a new job application. The API validates the input, enforces the unsubmitted/dateApplied coupling, persists the record, records an initial history snapshot, and returns the created application with a 201 response.", + "tags": ["applications", "create", "validation", "history"], + "complexity": "moderate", + "domainMeta": { + "entryPoint": "POST /api/applications", + "entryType": "http" + } + }, + { + "id": "flow:list-and-filter-applications", + "type": "flow", + "name": "List and Filter Applications", + "summary": "User views the paginated application list, optionally filtering by status, company category, job source, skills match, and archived flag, with configurable sort order. Returns a paginated response with items, page, limit, and total counts.", + "tags": ["applications", "list", "pagination", "filtering", "sorting"], + "complexity": "moderate", + "domainMeta": { + "entryPoint": "GET /api/applications", + "entryType": "http" + } + }, + { + "id": "flow:update-application", + "type": "flow", + "name": "Update Application", + "summary": "User edits an existing application's fields. The API validates the partial update payload, applies changes, records a diff-based history snapshot capturing before/after field values, and returns the updated application.", + "tags": ["applications", "update", "partial-update", "history", "diff"], + "complexity": "moderate", + "domainMeta": { + "entryPoint": "PATCH /api/applications/:id", + "entryType": "http" + } + }, + { + "id": "flow:archive-restore-application", + "type": "flow", + "name": "Archive and Restore Application", + "summary": "User soft-deletes an application by archiving it (sets isArchived=true), hiding it from the default list view. A separate restore flow reverses this. Both operations record history snapshots and return the updated application.", + "tags": ["applications", "archive", "restore", "soft-delete"], + "complexity": "simple", + "domainMeta": { + "entryPoint": "POST /api/applications/:id/archive", + "entryType": "http" + } + }, + { + "id": "flow:delete-application", + "type": "flow", + "name": "Delete Application", + "summary": "User permanently deletes a job application. The API removes the record and cascades the deletion to all child interview stages and history entries. Returns 204 No Content on success.", + "tags": ["applications", "delete", "cascade"], + "complexity": "simple", + "domainMeta": { + "entryPoint": "DELETE /api/applications/:id", + "entryType": "http" + } + }, + { + "id": "flow:manage-interview-stages", + "type": "flow", + "name": "Manage Interview Stages", + "summary": "User adds, edits, or removes interview stages within an application. Each operation verifies the parent application exists, performs the stage mutation, bumps the parent's updatedAt, and records a history snapshot.", + "tags": ["interview-stages", "crud", "pipeline", "history"], + "complexity": "moderate", + "domainMeta": { + "entryPoint": "POST /api/applications/:id/interview-stages", + "entryType": "http" + } + }, + { + "id": "flow:view-application-history", + "type": "flow", + "name": "View Application History", + "summary": "User opens the history panel to see a paginated, newest-first list of all changes to an application, including field-level before/after diffs for each recorded snapshot.", + "tags": ["history", "audit", "diffs", "pagination"], + "complexity": "moderate", + "domainMeta": { + "entryPoint": "GET /api/applications/:id/history", + "entryType": "http" + } + }, + { + "id": "flow:restore-application-version", + "type": "flow", + "name": "Restore Application to Historical Version", + "summary": "User selects a past snapshot and restores the application to that state. The API replays history from the nearest snapshot to the target version, rebuilds the application and stage records atomically, and records a restore event in history.", + "tags": ["history", "restore", "versioning", "time-travel", "snapshot"], + "complexity": "complex", + "domainMeta": { + "entryPoint": "POST /api/applications/:id/history/restore", + "entryType": "http" + } + }, + { + "id": "flow:csv-import", + "type": "flow", + "name": "CSV Import", + "summary": "User uploads a CSV file containing job application rows. The API parses the file (handling quoted multi-line fields), validates each row against the application schema, deduplicates, and upserts valid rows, returning a summary of imported, skipped, and errored rows.", + "tags": ["csv", "import", "bulk", "validation", "deduplication"], + "complexity": "complex", + "domainMeta": { + "entryPoint": "POST /api/applications/import", + "entryType": "http" + } + }, + { + "id": "flow:csv-export", + "type": "flow", + "name": "CSV Export", + "summary": "User downloads all their job applications as a CSV file. The API iterates through paginated application results and streams them to a CSV-formatted response, including all core fields.", + "tags": ["csv", "export", "bulk", "serialization"], + "complexity": "simple", + "domainMeta": { + "entryPoint": "GET /api/applications/export", + "entryType": "http" + } + }, + { + "id": "flow:database-migration", + "type": "flow", + "name": "Database Schema Migration", + "summary": "On server startup, each stack runs its migration tooling (Flyway, Prisma Migrate, Drizzle, golang-migrate, or a custom runner) to create or update the stack's PostgreSQL schema. Migrations use IF NOT EXISTS guards to be idempotent.", + "tags": ["migration", "database", "infrastructure", "startup"], + "complexity": "moderate", + "domainMeta": { + "entryPoint": "Server startup (automatic)", + "entryType": "event" + } + }, + { + "id": "step:create-application:validate-input", + "type": "step", + "name": "Validate Input", + "summary": "Parse and validate the incoming JSON body against the CreateApplicationSchema (Zod, Pydantic, or Bean Validation). Reject with 400 if required fields are missing or values are out of range.", + "tags": ["validation", "zod", "pydantic", "input"], + "complexity": "simple", + "filePath": "src/types/api.ts", + "lineRange": [0, 0] + }, + { + "id": "step:create-application:enforce-status-date-coupling", + "type": "step", + "name": "Enforce Status/Date Coupling", + "summary": "If dateApplied is absent, force the status to 'unsubmitted'. This invariant is enforced in service code across all stacks to prevent applications being marked as submitted without a recorded apply date.", + "tags": ["business-rule", "validation", "status", "date"], + "complexity": "simple", + "filePath": "src/services/application.service.ts", + "lineRange": [0, 0] + }, + { + "id": "step:create-application:persist-record", + "type": "step", + "name": "Persist Application Record", + "summary": "Insert the new application row into the database with a UUID primary key and current timestamps for createdAt/updatedAt. Includes all metadata fields (salary, company info, skills match, notes).", + "tags": ["database", "insert", "postgresql"], + "complexity": "simple", + "filePath": "src/services/application.service.ts", + "lineRange": [0, 0] + }, + { + "id": "step:create-application:record-history-snapshot", + "type": "step", + "name": "Record Initial History Snapshot", + "summary": "After successful insert, capture a full JSON snapshot of the new application into the history/audit table. Records the action description 'Application created' and initializes the history sequence counter.", + "tags": ["history", "snapshot", "audit"], + "complexity": "simple", + "filePath": "src/services/history.service.ts", + "lineRange": [0, 0] + }, + { + "id": "step:create-application:return-201-response", + "type": "step", + "name": "Return 201 Created Response", + "summary": "Serialize the created application (including its empty interview stages array) and return it with HTTP 201. Response shape is consistent across all stacks: id, companyName, positionTitle, status, createdAt, updatedAt, interviewStages.", + "tags": ["http", "response", "serialization"], + "complexity": "simple", + "filePath": "src/routes/applications.ts", + "lineRange": [0, 0] + }, + { + "id": "step:list-and-filter-applications:parse-query-params", + "type": "step", + "name": "Parse and Validate Query Parameters", + "summary": "Extract and coerce filter parameters from the query string: status (multi-value), companyCategory, jobSource, skillsMatch, isArchived, sort, page, limit. Apply defaults for missing params.", + "tags": ["validation", "query-params", "filtering"], + "complexity": "simple", + "filePath": "server/api/applications/index.get.ts", + "lineRange": [0, 0] + }, + { + "id": "step:list-and-filter-applications:build-filter-query", + "type": "step", + "name": "Build Dynamic Filter Query", + "summary": "Construct the database query with WHERE clauses for each active filter. Uses parameterized queries in all stacks (Drizzle, Prisma, Knex, asyncpg, sqlc, pgx) to avoid SQL injection. Handles multi-value status arrays with IN clauses.", + "tags": ["database", "query", "filtering", "sql"], + "complexity": "moderate", + "filePath": "src/services/application.service.ts", + "lineRange": [0, 0] + }, + { + "id": "step:list-and-filter-applications:apply-pagination", + "type": "step", + "name": "Apply Pagination", + "summary": "Execute COUNT(*) for total and SELECT with LIMIT/OFFSET for the page slice. Return paginated envelope: {items, total, page, limit}.", + "tags": ["pagination", "database", "count"], + "complexity": "simple", + "filePath": "src/services/application.service.ts", + "lineRange": [0, 0] + }, + { + "id": "step:list-and-filter-applications:eager-load-stages", + "type": "step", + "name": "Eager-Load Interview Stages", + "summary": "Fetch interview stages for all applications in the result page using a single JOIN or secondary query. Nest the stages array within each application object before returning.", + "tags": ["database", "join", "interview-stages", "eager-loading"], + "complexity": "simple", + "filePath": "src/services/application.service.ts", + "lineRange": [0, 0] + }, + { + "id": "step:list-and-filter-applications:return-paginated-response", + "type": "step", + "name": "Return Paginated Response", + "summary": "Serialize the application list and return the paginated envelope with HTTP 200. All stacks return the same shape: {items: Application[], total: number, page: number, limit: number}.", + "tags": ["http", "response", "serialization", "pagination"], + "complexity": "simple", + "filePath": "src/routes/applications.ts", + "lineRange": [0, 0] + }, + { + "id": "step:update-application:validate-partial-body", + "type": "step", + "name": "Validate Partial Update Body", + "summary": "Parse the PATCH request body against the UpdateApplicationSchema (all fields optional). Reject with 400 if any provided field fails type or range validation.", + "tags": ["validation", "partial-update", "zod"], + "complexity": "simple", + "filePath": "src/types/api.ts", + "lineRange": [0, 0] + }, + { + "id": "step:update-application:lookup-existing-record", + "type": "step", + "name": "Lookup Existing Record", + "summary": "Fetch the current application by ID. Return 404 if not found. The fetched record is used both to verify existence and to compute field-level diffs for the history entry.", + "tags": ["database", "lookup", "404"], + "complexity": "simple", + "filePath": "src/services/application.service.ts", + "lineRange": [0, 0] + }, + { + "id": "step:update-application:apply-changes", + "type": "step", + "name": "Apply Field Changes", + "summary": "Merge the partial update payload onto the existing record and persist the updated row with a new updatedAt timestamp. Only provided fields are written; absent fields remain unchanged.", + "tags": ["database", "update", "partial-update"], + "complexity": "simple", + "filePath": "src/services/application.service.ts", + "lineRange": [0, 0] + }, + { + "id": "step:update-application:compute-field-diffs", + "type": "step", + "name": "Compute Field-Level Diffs", + "summary": "Compare before and after snapshots to produce a list of changed fields with old and new values. Used to populate the history entry's changes array, making the history panel informative.", + "tags": ["diff", "history", "audit", "field-comparison"], + "complexity": "moderate", + "filePath": "src/services/history.service.ts", + "lineRange": [0, 0] + }, + { + "id": "step:update-application:record-history-snapshot", + "type": "step", + "name": "Record History Snapshot", + "summary": "Persist a history/snapshot record capturing the full application state after the update, along with the field-level diff and an action description. Increments the history sequence counter atomically.", + "tags": ["history", "snapshot", "audit", "sequence"], + "complexity": "simple", + "filePath": "src/services/history.service.ts", + "lineRange": [0, 0] + }, + { + "id": "step:update-application:return-updated-application", + "type": "step", + "name": "Return Updated Application", + "summary": "Serialize and return the updated application with HTTP 200, including its current interview stages.", + "tags": ["http", "response", "serialization"], + "complexity": "simple", + "filePath": "src/routes/applications.ts", + "lineRange": [0, 0] + }, + { + "id": "step:archive-restore-application:validate-application-exists", + "type": "step", + "name": "Validate Application Exists", + "summary": "Fetch the application by ID. Return 404 if not found. Also validates the current archive state (archive on already-archived or restore on active may be no-ops depending on the stack).", + "tags": ["validation", "lookup", "404"], + "complexity": "simple", + "filePath": "src/services/application.service.ts", + "lineRange": [0, 0] + }, + { + "id": "step:archive-restore-application:toggle-archived-flag", + "type": "step", + "name": "Toggle Archived Flag", + "summary": "Set isArchived=true (archive) or isArchived=false (restore) and update the updatedAt timestamp. Single UPDATE statement targeting the specific application row.", + "tags": ["database", "update", "archive", "soft-delete"], + "complexity": "simple", + "filePath": "src/services/application.service.ts", + "lineRange": [0, 0] + }, + { + "id": "step:archive-restore-application:record-archive-history", + "type": "step", + "name": "Record Archive/Restore History", + "summary": "Capture a history snapshot labelled 'Application archived' or 'Application restored' so the audit trail reflects the lifecycle event.", + "tags": ["history", "audit", "snapshot"], + "complexity": "simple", + "filePath": "src/services/history.service.ts", + "lineRange": [0, 0] + }, + { + "id": "step:archive-restore-application:return-updated-application", + "type": "step", + "name": "Return Updated Application", + "summary": "Return the application with its new isArchived state and HTTP 200, so the UI can immediately reflect the change.", + "tags": ["http", "response"], + "complexity": "simple", + "filePath": "src/routes/applications.ts", + "lineRange": [0, 0] + }, + { + "id": "step:delete-application:validate-application-exists", + "type": "step", + "name": "Validate Application Exists", + "summary": "Fetch the application by ID and return 404 if not found, before attempting deletion.", + "tags": ["validation", "lookup", "404"], + "complexity": "simple", + "filePath": "src/services/application.service.ts", + "lineRange": [0, 0] + }, + { + "id": "step:delete-application:cascade-delete-children", + "type": "step", + "name": "Cascade Delete Child Records", + "summary": "Remove all related interview stages and history entries before deleting the application row. Most relational stacks use ON DELETE CASCADE in the schema; DynamoDB performs explicit batch deletes by querying on the partition key.", + "tags": ["database", "cascade", "delete", "transaction"], + "complexity": "moderate", + "filePath": "src/services/application.service.ts", + "lineRange": [0, 0] + }, + { + "id": "step:delete-application:return-204", + "type": "step", + "name": "Return 204 No Content", + "summary": "After successful deletion, return HTTP 204 with no body. Callers must check for 204 before parsing JSON to avoid parse errors.", + "tags": ["http", "response", "204"], + "complexity": "simple", + "filePath": "src/routes/applications.ts", + "lineRange": [0, 0] + }, + { + "id": "step:manage-interview-stages:validate-parent-application", + "type": "step", + "name": "Validate Parent Application Exists", + "summary": "Before any stage mutation, verify the parent application exists by ID. Return 404 if not found. This prevents orphaned stage records.", + "tags": ["validation", "lookup", "404", "parent-record"], + "complexity": "simple", + "filePath": "src/services/stages.service.ts", + "lineRange": [0, 0] + }, + { + "id": "step:manage-interview-stages:validate-stage-input", + "type": "step", + "name": "Validate Stage Input", + "summary": "Parse the request body against the CreateInterviewStageSchema or UpdateInterviewStageSchema. Validates name, order, type, scheduledDate, notes, and completed fields.", + "tags": ["validation", "schema", "interview-stages"], + "complexity": "simple", + "filePath": "src/types/api.ts", + "lineRange": [0, 0] + }, + { + "id": "step:manage-interview-stages:persist-stage-change", + "type": "step", + "name": "Persist Stage Change", + "summary": "Execute the INSERT (create), UPDATE (edit), or DELETE (remove) on the interview_stages table. Completion auto-dates are applied when the completed flag is toggled.", + "tags": ["database", "crud", "interview-stages"], + "complexity": "simple", + "filePath": "src/services/stages.service.ts", + "lineRange": [0, 0] + }, + { + "id": "step:manage-interview-stages:bump-parent-updated-at", + "type": "step", + "name": "Bump Parent Application updatedAt", + "summary": "Update the parent application's updatedAt timestamp after any stage change. This keeps the application at the top of the default sort order when its pipeline is actively being worked.", + "tags": ["database", "timestamp", "parent-record", "sort-order"], + "complexity": "simple", + "filePath": "src/services/stages.service.ts", + "lineRange": [0, 0] + }, + { + "id": "step:manage-interview-stages:record-stage-history", + "type": "step", + "name": "Record Stage History Snapshot", + "summary": "Capture a history entry describing the stage operation ('Stage added', 'Stage updated', 'Stage removed') on the parent application's history timeline.", + "tags": ["history", "audit", "snapshot", "interview-stages"], + "complexity": "simple", + "filePath": "src/services/history.service.ts", + "lineRange": [0, 0] + }, + { + "id": "step:manage-interview-stages:return-response", + "type": "step", + "name": "Return Stage Response", + "summary": "Return the created or updated stage with HTTP 201/200, or 204 on deletion. The response includes all stage fields so the UI can update its local state.", + "tags": ["http", "response", "serialization"], + "complexity": "simple", + "filePath": "src/routes/applications.ts", + "lineRange": [0, 0] + }, + { + "id": "step:view-application-history:validate-application-exists", + "type": "step", + "name": "Validate Application Exists", + "summary": "Look up the application by ID before fetching its history. Return 404 if not found.", + "tags": ["validation", "lookup", "404"], + "complexity": "simple", + "filePath": "src/services/history.service.ts", + "lineRange": [0, 0] + }, + { + "id": "step:view-application-history:query-history-entries", + "type": "step", + "name": "Query History Entries", + "summary": "Fetch history snapshots for the application, newest-first, with optional pagination (page/limit). Returns raw snapshot rows from the application_history table or equivalent.", + "tags": ["database", "query", "history", "pagination"], + "complexity": "simple", + "filePath": "src/services/history.service.ts", + "lineRange": [0, 0] + }, + { + "id": "step:view-application-history:compute-field-diffs", + "type": "step", + "name": "Compute Field Diffs Per Entry", + "summary": "For each history entry, compare the current snapshot to the prior one to produce the field-level changes list. Some stacks store diffs at write time; others compute them on read from adjacent snapshots.", + "tags": ["diff", "computation", "history", "field-comparison"], + "complexity": "moderate", + "filePath": "internal/service/history.go", + "lineRange": [0, 0] + }, + { + "id": "step:view-application-history:return-history-list", + "type": "step", + "name": "Return History List", + "summary": "Serialize and return the paginated history entries with action descriptions, timestamps, and per-field change arrays for rendering in the history panel.", + "tags": ["http", "response", "serialization", "history"], + "complexity": "simple", + "filePath": "internal/handler/history.go", + "lineRange": [0, 0] + }, + { + "id": "step:restore-application-version:validate-target-sequence", + "type": "step", + "name": "Validate Target Sequence or Snapshot ID", + "summary": "Parse and validate the restore target from the request body: either a snapshot ID or a target event sequence number. Return 400 for invalid input, 404 if the target does not exist.", + "tags": ["validation", "input", "sequence", "snapshot"], + "complexity": "simple", + "filePath": "server/api/applications/[id]/events/restore.post.ts", + "lineRange": [0, 0] + }, + { + "id": "step:restore-application-version:find-nearest-snapshot", + "type": "step", + "name": "Find Nearest Snapshot", + "summary": "Locate the most recent snapshot at or before the target sequence. This is the starting point for replay, minimizing the number of forward patches that must be applied.", + "tags": ["database", "snapshot", "lookup", "event-sourcing"], + "complexity": "moderate", + "filePath": "server/api/applications/[id]/snapshots/latest.get.ts", + "lineRange": [0, 0] + }, + { + "id": "step:restore-application-version:replay-events", + "type": "step", + "name": "Replay Events to Target", + "summary": "Apply forward Immer patches from the nearest snapshot up to the target sequence number to reconstruct the application state at that point in time.", + "tags": ["event-sourcing", "immer", "patches", "replay", "time-travel"], + "complexity": "complex", + "filePath": "server/services/event.service.ts", + "lineRange": [0, 0] + }, + { + "id": "step:restore-application-version:overwrite-current-state", + "type": "step", + "name": "Overwrite Current Application State", + "summary": "Atomically replace the current application and stage records with the reconstructed historical state. Rebuilds GSI keys (DynamoDB) or updates the relational row in a transaction.", + "tags": ["database", "transaction", "restore", "atomic"], + "complexity": "complex", + "filePath": "src/services/application.service.ts", + "lineRange": [0, 0] + }, + { + "id": "step:restore-application-version:record-restore-event", + "type": "step", + "name": "Record Restore History Event", + "summary": "Append a new history entry or event describing the version restore, so the audit trail shows when and to what point a rollback occurred.", + "tags": ["history", "audit", "restore", "event"], + "complexity": "simple", + "filePath": "src/services/history.service.ts", + "lineRange": [0, 0] + }, + { + "id": "step:restore-application-version:return-restored-application", + "type": "step", + "name": "Return Restored Application", + "summary": "Serialize and return the fully restored application state so the UI can immediately reflect the historical version.", + "tags": ["http", "response", "serialization"], + "complexity": "simple", + "filePath": "src/routes/applications.ts", + "lineRange": [0, 0] + }, + { + "id": "step:csv-import:receive-file-upload", + "type": "step", + "name": "Receive File Upload", + "summary": "Accept the CSV file via multipart/form-data POST. Extract the file buffer from the request for parsing.", + "tags": ["http", "file-upload", "multipart"], + "complexity": "simple", + "filePath": "internal/handler/csv.go", + "lineRange": [0, 0] + }, + { + "id": "step:csv-import:parse-csv-content", + "type": "step", + "name": "Parse CSV Content", + "summary": "Run the character-by-character CSV parser that correctly handles quoted fields containing embedded newlines, commas, and escaped double-quotes. Produces a row array of string maps keyed by header name.", + "tags": ["csv", "parsing", "quoted-fields", "multi-line"], + "complexity": "complex", + "filePath": "src/services/csv.service.ts", + "lineRange": [0, 0] + }, + { + "id": "step:csv-import:validate-each-row", + "type": "step", + "name": "Validate Each Row", + "summary": "Apply the application creation schema to each parsed row, collecting per-row validation errors. Invalid rows are skipped with an error message rather than aborting the entire import.", + "tags": ["validation", "per-row", "error-collection"], + "complexity": "moderate", + "filePath": "src/services/csv.service.ts", + "lineRange": [0, 0] + }, + { + "id": "step:csv-import:deduplicate-rows", + "type": "step", + "name": "Deduplicate Rows", + "summary": "Check each valid row against existing applications using jobPostingUrl (primary) or the (companyName, positionTitle) compound key (fallback). Matching rows are treated as updates; new rows are inserts.", + "tags": ["deduplication", "upsert", "duplicate-detection"], + "complexity": "moderate", + "filePath": "internal/service/csv.go", + "lineRange": [0, 0] + }, + { + "id": "step:csv-import:upsert-applications", + "type": "step", + "name": "Upsert Applications", + "summary": "Create or update each deduplicated row through the standard application service layer, applying the same business rules (status/date coupling, etc.) as the direct API.", + "tags": ["database", "upsert", "create", "update", "business-rules"], + "complexity": "moderate", + "filePath": "src/services/csv.service.ts", + "lineRange": [0, 0] + }, + { + "id": "step:csv-import:return-import-summary", + "type": "step", + "name": "Return Import Summary", + "summary": "Return a JSON summary with counts of imported, skipped (duplicates), and errored rows, plus per-row error details so the user can correct and re-import failed entries.", + "tags": ["http", "response", "summary", "errors"], + "complexity": "simple", + "filePath": "src/routes/applications.ts", + "lineRange": [0, 0] + }, + { + "id": "step:csv-export:fetch-all-applications", + "type": "step", + "name": "Fetch All Applications via Pagination Loop", + "summary": "Repeatedly call the list service with increasing page offsets until all applications are fetched. No UI filters applied — export always returns the full dataset.", + "tags": ["database", "pagination", "bulk-fetch"], + "complexity": "simple", + "filePath": "src/services/csv.service.ts", + "lineRange": [0, 0] + }, + { + "id": "step:csv-export:serialize-to-csv", + "type": "step", + "name": "Serialize Applications to CSV", + "summary": "Convert each application record to a CSV row using the defined column order. Quote string fields containing commas or newlines. Prepend the header row.", + "tags": ["serialization", "csv", "formatting"], + "complexity": "simple", + "filePath": "src/services/csv.service.ts", + "lineRange": [0, 0] + }, + { + "id": "step:csv-export:stream-csv-response", + "type": "step", + "name": "Stream CSV Response", + "summary": "Return the CSV content with Content-Type: text/csv and Content-Disposition: attachment headers so the browser triggers a file download.", + "tags": ["http", "streaming", "download", "headers"], + "complexity": "simple", + "filePath": "internal/handler/csv.go", + "lineRange": [0, 0] + }, + { + "id": "step:database-migration:detect-pending-migrations", + "type": "step", + "name": "Detect Pending Migrations", + "summary": "Check the schema_migrations tracking table (or ORM equivalent) to identify which migration files have not yet been applied to the target schema.", + "tags": ["migration", "tracking", "database"], + "complexity": "simple", + "filePath": "migrations/run.py", + "lineRange": [0, 0] + }, + { + "id": "step:database-migration:create-schema", + "type": "step", + "name": "Create Schema if Not Exists", + "summary": "Issue CREATE SCHEMA IF NOT EXISTS to isolate this stack's tables from other implementations sharing the same PostgreSQL database.", + "tags": ["database", "schema", "isolation", "idempotent"], + "complexity": "simple", + "filePath": "migrations/run.py", + "lineRange": [0, 0] + }, + { + "id": "step:database-migration:apply-sql-files", + "type": "step", + "name": "Apply SQL Migration Files", + "summary": "Execute each pending SQL migration file in order, creating tables, enums, indexes, and constraints. Record applied migration names in the tracking table.", + "tags": ["database", "ddl", "sql", "apply"], + "complexity": "moderate", + "filePath": "internal/migrations/001_initial.up.sql", + "lineRange": [0, 0] + }, + { + "id": "step:database-migration:verify-connectivity", + "type": "step", + "name": "Verify Database Connectivity", + "summary": "After migrations complete, perform a health ping to confirm the connection pool is live before accepting HTTP traffic.", + "tags": ["database", "health", "connectivity", "startup"], + "complexity": "simple", + "filePath": "cmd/server/main.go", + "lineRange": [0, 0] + } + ], + "edges": [ + { "source": "domain:application-management", "target": "flow:create-application", "type": "contains_flow", "direction": "forward", "weight": 1.0 }, + { "source": "domain:application-management", "target": "flow:list-and-filter-applications", "type": "contains_flow", "direction": "forward", "weight": 1.0 }, + { "source": "domain:application-management", "target": "flow:update-application", "type": "contains_flow", "direction": "forward", "weight": 1.0 }, + { "source": "domain:application-management", "target": "flow:archive-restore-application", "type": "contains_flow", "direction": "forward", "weight": 1.0 }, + { "source": "domain:application-management", "target": "flow:delete-application", "type": "contains_flow", "direction": "forward", "weight": 1.0 }, + { "source": "domain:interview-stage-management", "target": "flow:manage-interview-stages", "type": "contains_flow", "direction": "forward", "weight": 1.0 }, + { "source": "domain:history-tracking", "target": "flow:view-application-history", "type": "contains_flow", "direction": "forward", "weight": 1.0 }, + { "source": "domain:history-tracking", "target": "flow:restore-application-version", "type": "contains_flow", "direction": "forward", "weight": 1.0 }, + { "source": "domain:data-portability", "target": "flow:csv-import", "type": "contains_flow", "direction": "forward", "weight": 1.0 }, + { "source": "domain:data-portability", "target": "flow:csv-export", "type": "contains_flow", "direction": "forward", "weight": 1.0 }, + { "source": "domain:platform-infrastructure", "target": "flow:database-migration", "type": "contains_flow", "direction": "forward", "weight": 1.0 }, + + { "source": "flow:create-application", "target": "step:create-application:validate-input", "type": "flow_step", "direction": "forward", "weight": 0.2 }, + { "source": "flow:create-application", "target": "step:create-application:enforce-status-date-coupling", "type": "flow_step", "direction": "forward", "weight": 0.4 }, + { "source": "flow:create-application", "target": "step:create-application:persist-record", "type": "flow_step", "direction": "forward", "weight": 0.6 }, + { "source": "flow:create-application", "target": "step:create-application:record-history-snapshot", "type": "flow_step", "direction": "forward", "weight": 0.8 }, + { "source": "flow:create-application", "target": "step:create-application:return-201-response", "type": "flow_step", "direction": "forward", "weight": 1.0 }, + + { "source": "flow:list-and-filter-applications", "target": "step:list-and-filter-applications:parse-query-params", "type": "flow_step", "direction": "forward", "weight": 0.2 }, + { "source": "flow:list-and-filter-applications", "target": "step:list-and-filter-applications:build-filter-query", "type": "flow_step", "direction": "forward", "weight": 0.4 }, + { "source": "flow:list-and-filter-applications", "target": "step:list-and-filter-applications:apply-pagination", "type": "flow_step", "direction": "forward", "weight": 0.6 }, + { "source": "flow:list-and-filter-applications", "target": "step:list-and-filter-applications:eager-load-stages", "type": "flow_step", "direction": "forward", "weight": 0.8 }, + { "source": "flow:list-and-filter-applications", "target": "step:list-and-filter-applications:return-paginated-response", "type": "flow_step", "direction": "forward", "weight": 1.0 }, + + { "source": "flow:update-application", "target": "step:update-application:validate-partial-body", "type": "flow_step", "direction": "forward", "weight": 0.2 }, + { "source": "flow:update-application", "target": "step:update-application:lookup-existing-record", "type": "flow_step", "direction": "forward", "weight": 0.3 }, + { "source": "flow:update-application", "target": "step:update-application:apply-changes", "type": "flow_step", "direction": "forward", "weight": 0.5 }, + { "source": "flow:update-application", "target": "step:update-application:compute-field-diffs", "type": "flow_step", "direction": "forward", "weight": 0.7 }, + { "source": "flow:update-application", "target": "step:update-application:record-history-snapshot", "type": "flow_step", "direction": "forward", "weight": 0.8 }, + { "source": "flow:update-application", "target": "step:update-application:return-updated-application", "type": "flow_step", "direction": "forward", "weight": 1.0 }, + + { "source": "flow:archive-restore-application", "target": "step:archive-restore-application:validate-application-exists", "type": "flow_step", "direction": "forward", "weight": 0.3 }, + { "source": "flow:archive-restore-application", "target": "step:archive-restore-application:toggle-archived-flag", "type": "flow_step", "direction": "forward", "weight": 0.6 }, + { "source": "flow:archive-restore-application", "target": "step:archive-restore-application:record-archive-history", "type": "flow_step", "direction": "forward", "weight": 0.8 }, + { "source": "flow:archive-restore-application", "target": "step:archive-restore-application:return-updated-application", "type": "flow_step", "direction": "forward", "weight": 1.0 }, + + { "source": "flow:delete-application", "target": "step:delete-application:validate-application-exists", "type": "flow_step", "direction": "forward", "weight": 0.3 }, + { "source": "flow:delete-application", "target": "step:delete-application:cascade-delete-children", "type": "flow_step", "direction": "forward", "weight": 0.7 }, + { "source": "flow:delete-application", "target": "step:delete-application:return-204", "type": "flow_step", "direction": "forward", "weight": 1.0 }, + + { "source": "flow:manage-interview-stages", "target": "step:manage-interview-stages:validate-parent-application", "type": "flow_step", "direction": "forward", "weight": 0.2 }, + { "source": "flow:manage-interview-stages", "target": "step:manage-interview-stages:validate-stage-input", "type": "flow_step", "direction": "forward", "weight": 0.3 }, + { "source": "flow:manage-interview-stages", "target": "step:manage-interview-stages:persist-stage-change", "type": "flow_step", "direction": "forward", "weight": 0.5 }, + { "source": "flow:manage-interview-stages", "target": "step:manage-interview-stages:bump-parent-updated-at", "type": "flow_step", "direction": "forward", "weight": 0.7 }, + { "source": "flow:manage-interview-stages", "target": "step:manage-interview-stages:record-stage-history", "type": "flow_step", "direction": "forward", "weight": 0.8 }, + { "source": "flow:manage-interview-stages", "target": "step:manage-interview-stages:return-response", "type": "flow_step", "direction": "forward", "weight": 1.0 }, + + { "source": "flow:view-application-history", "target": "step:view-application-history:validate-application-exists", "type": "flow_step", "direction": "forward", "weight": 0.3 }, + { "source": "flow:view-application-history", "target": "step:view-application-history:query-history-entries", "type": "flow_step", "direction": "forward", "weight": 0.5 }, + { "source": "flow:view-application-history", "target": "step:view-application-history:compute-field-diffs", "type": "flow_step", "direction": "forward", "weight": 0.7 }, + { "source": "flow:view-application-history", "target": "step:view-application-history:return-history-list", "type": "flow_step", "direction": "forward", "weight": 1.0 }, + + { "source": "flow:restore-application-version", "target": "step:restore-application-version:validate-target-sequence", "type": "flow_step", "direction": "forward", "weight": 0.2 }, + { "source": "flow:restore-application-version", "target": "step:restore-application-version:find-nearest-snapshot", "type": "flow_step", "direction": "forward", "weight": 0.3 }, + { "source": "flow:restore-application-version", "target": "step:restore-application-version:replay-events", "type": "flow_step", "direction": "forward", "weight": 0.5 }, + { "source": "flow:restore-application-version", "target": "step:restore-application-version:overwrite-current-state", "type": "flow_step", "direction": "forward", "weight": 0.7 }, + { "source": "flow:restore-application-version", "target": "step:restore-application-version:record-restore-event", "type": "flow_step", "direction": "forward", "weight": 0.8 }, + { "source": "flow:restore-application-version", "target": "step:restore-application-version:return-restored-application", "type": "flow_step", "direction": "forward", "weight": 1.0 }, + + { "source": "flow:csv-import", "target": "step:csv-import:receive-file-upload", "type": "flow_step", "direction": "forward", "weight": 0.2 }, + { "source": "flow:csv-import", "target": "step:csv-import:parse-csv-content", "type": "flow_step", "direction": "forward", "weight": 0.3 }, + { "source": "flow:csv-import", "target": "step:csv-import:validate-each-row", "type": "flow_step", "direction": "forward", "weight": 0.5 }, + { "source": "flow:csv-import", "target": "step:csv-import:deduplicate-rows", "type": "flow_step", "direction": "forward", "weight": 0.7 }, + { "source": "flow:csv-import", "target": "step:csv-import:upsert-applications", "type": "flow_step", "direction": "forward", "weight": 0.8 }, + { "source": "flow:csv-import", "target": "step:csv-import:return-import-summary", "type": "flow_step", "direction": "forward", "weight": 1.0 }, + + { "source": "flow:csv-export", "target": "step:csv-export:fetch-all-applications", "type": "flow_step", "direction": "forward", "weight": 0.3 }, + { "source": "flow:csv-export", "target": "step:csv-export:serialize-to-csv", "type": "flow_step", "direction": "forward", "weight": 0.7 }, + { "source": "flow:csv-export", "target": "step:csv-export:stream-csv-response", "type": "flow_step", "direction": "forward", "weight": 1.0 }, + + { "source": "flow:database-migration", "target": "step:database-migration:detect-pending-migrations", "type": "flow_step", "direction": "forward", "weight": 0.3 }, + { "source": "flow:database-migration", "target": "step:database-migration:create-schema", "type": "flow_step", "direction": "forward", "weight": 0.4 }, + { "source": "flow:database-migration", "target": "step:database-migration:apply-sql-files", "type": "flow_step", "direction": "forward", "weight": 0.7 }, + { "source": "flow:database-migration", "target": "step:database-migration:verify-connectivity", "type": "flow_step", "direction": "forward", "weight": 1.0 }, + + { "source": "domain:application-management", "target": "domain:history-tracking", "type": "cross_domain", "direction": "forward", "description": "Every application create, update, archive, and restore triggers a history snapshot in the History Tracking domain", "weight": 0.8 }, + { "source": "domain:interview-stage-management", "target": "domain:history-tracking", "type": "cross_domain", "direction": "forward", "description": "Every stage add, update, and delete records a history entry on the parent application's audit timeline", "weight": 0.7 }, + { "source": "domain:interview-stage-management", "target": "domain:application-management", "type": "cross_domain", "direction": "forward", "description": "Interview stages are child records of applications; stage mutations validate and update the parent application", "weight": 0.9 }, + { "source": "domain:data-portability", "target": "domain:application-management", "type": "cross_domain", "direction": "forward", "description": "CSV import delegates to application create/update service methods; CSV export consumes the application list query", "weight": 0.8 }, + { "source": "domain:platform-infrastructure", "target": "domain:application-management", "type": "cross_domain", "direction": "forward", "description": "Database schema migrations create and maintain the tables and enums that Application Management reads and writes", "weight": 0.6 }, + { "source": "domain:platform-infrastructure", "target": "domain:history-tracking", "type": "cross_domain", "direction": "forward", "description": "The application_history and events tables created by migrations underpin the History Tracking domain", "weight": 0.6 } + ], + "layers": [], + "tour": [] +} diff --git a/CLAUDE.md b/CLAUDE.md index ea3981d0..73ebebb1 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -9,6 +9,7 @@ Monorepo with multiple frontend+backend implementation pairs sharing a single Po - **Worktree Isolation**: Complex operations use isolated worktrees at `../-[timestamp]`. Always specify `origin/main` as the base when creating: `git worktree add "$WORKTREE_PATH" -b "$BRANCH_NAME" origin/main` — omitting the start-point defaults to the current HEAD, which causes the PR to include unintended commits. After a worktree agent completes, always verify the commit landed on a feature branch — not on `main` — by checking `git log --oneline --decorate -3`. If the commit is on `main`, first create a feature branch from it and push it (`git checkout -b && git push -u origin `), then reset local main (`git checkout main && git reset --hard origin/main`). - **Validation Chain**: `build:*` → `lint:*` → `test:*` → `test:e2e:*` - **Script Naming**: Scripts follow `verb:package-name` (e.g., `build:react-next-ui`, `lint:angular-ui`). Use `:all` suffix for scripts that run across all packages. `test:e2e:*` uses the UI package name (e.g., `test:e2e:react-next-ui`, `test:e2e:tanstack-ui`). When adding a new implementation, add per-package scripts for every verb (`dev`, `build`, `lint`, `test`, `ci`, `audit:ci`, `validate`) and add each to its `*:all` script and to `scripts/stop-all.sh`. After adding, cross-check that sibling stacks also have `validate:*` and `ci:*` shorthands — these silently fall behind when new stacks are added. Use the same stack name across all verbs (e.g. `migrate:express-api` aliasing `migrate:express`) so generic `npm run "verb:$STACK"` invocations from `validate.sh` work for every stack. When adding a new script group (new verb pattern like `validate:*`), also update `README.md` — add a TOC entry and a usage section so the pattern is discoverable. Do not wait to be asked. +- **README TOC**: When adding a section or subsection to `README.md`, add a matching TOC entry. Anchor format: lowercase, spaces → `-`, drop special characters except hyphens. Subsections indent two spaces under their parent. - **Parallel Execution**: 3+ items use Task tool subagents - **Spec First**: When planning a new feature, the first implementation step should be to write the spec to `specs/-/spec.md` - **Spell Checker**: When cspell flags a valid term (tool names, libraries, technical jargon), add it to `cspell.config.yaml` under `words` @@ -21,6 +22,7 @@ Monorepo with multiple frontend+backend implementation pairs sharing a single Po - **Agent Skills Policy**: When responding to PR review feedback, do not directly apply reviewer suggestions to files in `.agents/skills/` — post a reply noting the suggestion will be addressed upstream instead. Skills sourced from `WhatIfWeDigDeeper/agent-skills` (including `pr-comments`, `ship-it`, `learn`, `playwright-cli`, etc.) are maintained upstream; deliberate version upgrades or syncs via dedicated PRs are fine. Sync: `npx skills add -y whatifwedigdeeper/agent-skills` — repo-wide, diff before committing. Only project-owned files (`scripts/`, `.vscode/`, `docs/`, `fastapi/`, application source) are in-scope for directly applying reviewer feedback. - **Skills directory layout**: `.agent/skills/` (singular) holds symlinks into `.agents/skills/` (plural, actual files); both gitignored. `.claude/skills/` mixes tracked project-local skills and gitignored symlinks to upstream skills. Reinstall from `skills-lock.json` with `npx skills add`. - **gitignore trailing slash doesn't match symlinks**: Patterns like `foo/` only match real directories — omit trailing slash for symlink entries. +- **gitignore slash anchors pattern to root**: A pattern with an internal `/` (like `foo/bar/`) is anchored to the `.gitignore` root — use `**/foo/bar/` to match nested directories too. ## Per-Stack Guidance @@ -157,6 +159,7 @@ Commands that require `dangerouslyDisableSandbox: true`: - `./gradlew` commands — sandbox has no Java Runtime; prepend `export JAVA_HOME=$(/usr/libexec/java_home)` Additional notes: +- **Bash write loops silently fail in sandbox**: `for f in ...; do cp ...; done` loops writing to project subdirs silently no-op. Use `python3 -c "import shutil; shutil.copy2(src, dst)"` with `dangerouslyDisableSandbox: true` instead. - **Heredoc in sandbox**: `$(cat <<'EOF'...EOF)` in commit messages fails in sandbox — use plain quoted strings or write to `$TMPDIR/commit-msg.txt` and use `git commit -F` - **`uv sync`**: Requires sandbox override for network; `uv run python -m src` works in sandbox after initial sync - **`permissions.allow` vs `sandbox.network.allowedHosts`**: `WebFetch(*)` in permissions controls whether the tool can be called without prompting — it does NOT bypass sandbox network restrictions. External hosts also need `sandbox.network.allowedHosts` in `.claude/settings.json`; `github.com` and `registry.npmjs.org` are already added diff --git a/README.md b/README.md index a086a7ef..912b6907 100644 --- a/README.md +++ b/README.md @@ -34,6 +34,9 @@ A full-stack job application tracking system with multiple technology stack impl - [Build Verification](#build-verification) - [Per-Stack Validation](#per-stack-validation) - [CI Workflows](#ci-workflows) +- [Codebase Knowledge Graphs](#codebase-knowledge-graphs) + - [Viewing a Graph](#viewing-a-graph) + - [Domain Analysis (Business Flows)](#domain-analysis-business-flows) - [Development Tools](#development-tools) - [Skills](#skills) - [VS Code Debug Configurations](#vs-code-debug-configurations) @@ -499,6 +502,60 @@ npm run validate:all The **Claude Code Review** workflow (`claude-code-review`) is triggered manually — it does not run automatically on pull requests. To run it, go to **Actions → Claude Code Review → Run workflow** and enter the PR number. +## Codebase Knowledge Graphs + +The 20 stack directories listed below each have a navigable knowledge graph produced by the [understand-anything](https://github.com/WhatIfWeDigDeeper/understand-anything) plugin. Graphs map files, functions, classes, and their relationships into architectural layers with a guided tour for onboarding. `angular-spring-ui/` is the one implementation stack not yet covered — its knowledge graph is deferred to a follow-up. + +| Stack | Directory | +|-------|-----------| +| Lambda API (Hono/DynamoDB) | `lambda-api/` | +| Lambda React UI | `lambda-react-ui/` | +| Express API (Prisma) | `api/` | +| Koa API | `koa-api/` | +| React UI | `react-ui/` | +| Vue UI | `vue-ui/` | +| Nuxt API (Drizzle) | `nuxt-api/` | +| Hono API (Drizzle) | `hono-api/` | +| SvelteKit UI | `svelte-ui/` | +| NestJS API (Drizzle) | `nest-api/` | +| TanStack Router UI | `tanstack-ui/` | +| TanStack Start SSR UI | `tanstack-start-ui/` | +| FastAPI (Python) | `fastapi/` | +| Angular UI | `angular-ui/` | +| Go Gin API | `go-api/` | +| Spring Boot API | `spring-api/` | +| GraphQL Yoga API | `yoga-api/` | +| React Apollo UI | `react-apollo-ui/` | +| NestJS gRPC History API | `nest-history-api/` | +| Ruby on Rails API | `rails-api/` | + +### Viewing a Graph + +The dashboard requires the [understand-anything](https://github.com/WhatIfWeDigDeeper/understand-anything) plugin. Once installed, launch it for any analyzed stack: + +```bash +PLUGIN=~/.claude/plugins/cache/understand-anything/understand-anything/ +cd "$PLUGIN/packages/dashboard" +GRAPH_DIR=/path/to/application-tracker/ npx vite --host 127.0.0.1 +``` + +The server prints a tokenized URL — open the full URL including `?token=` in your browser. + +### Domain Analysis (Business Flows) + +Run `/understand-anything:understand-domain` in Claude Code to generate a `domain-graph.json` that maps business flows and their steps. Domain graphs are generated on demand and are not committed alongside the knowledge graphs. + +Invoke it either from the repo root, passing the stack directory as an argument: + +``` +/understand-anything:understand-domain lambda-api +/understand-anything:understand-domain react-ui +``` + +…or from inside a stack directory with no argument (it analyzes the current working directory). + +See [specs/029-understand-codebase-graphs/spec.md](specs/029-understand-codebase-graphs/spec.md) for full details. + ## Development Tools This repository includes Claude Code commands and skills for common development tasks: diff --git a/angular-ui/.understand-anything/.understandignore b/angular-ui/.understand-anything/.understandignore new file mode 100644 index 00000000..6fc4c503 --- /dev/null +++ b/angular-ui/.understand-anything/.understandignore @@ -0,0 +1,6 @@ +# .understandignore — patterns for files/dirs to exclude from analysis +# Built-in defaults (always excluded): node_modules/, .git/, dist/, build/, *.lock + +# Exclude test output and static assets +test-results/ +public/ diff --git a/angular-ui/.understand-anything/knowledge-graph.json b/angular-ui/.understand-anything/knowledge-graph.json new file mode 100644 index 00000000..6c1cf582 --- /dev/null +++ b/angular-ui/.understand-anything/knowledge-graph.json @@ -0,0 +1,522 @@ +{ + "nodes": [ + { + "id": "file:src/main.ts", + "type": "file", + "name": "main.ts", + "filePath": "src/main.ts", + "summary": "Application bootstrap entry point. Calls bootstrapApplication with AppComponent and appConfig to launch the Angular standalone app.", + "tags": ["entry-point", "bootstrap", "angular", "typescript"] + }, + { + "id": "file:src/app/app.config.ts", + "type": "file", + "name": "app.config.ts", + "filePath": "src/app/app.config.ts", + "summary": "Root application configuration. Provides Zone.js change detection with event coalescing, the Angular router, and HttpClient for HTTP communication.", + "tags": ["config", "angular", "providers", "http", "router", "typescript"] + }, + { + "id": "file:src/app/app.routes.ts", + "type": "file", + "name": "app.routes.ts", + "filePath": "src/app/app.routes.ts", + "summary": "Defines lazy-loaded routes: list view at '/', detail/new at 'applications/:id' and 'applications/new' (both guarded by unsavedChangesGuard), and a wildcard redirect.", + "tags": ["routing", "angular", "lazy-loading", "typescript"] + }, + { + "id": "file:src/app/app.component.ts", + "type": "file", + "name": "app.component.ts", + "filePath": "src/app/app.component.ts", + "summary": "Root shell component. Renders HeaderComponent and a inside a max-width container. Standalone, imports RouterOutlet and HeaderComponent.", + "tags": ["component", "angular", "shell", "standalone", "typescript"] + }, + { + "id": "file:src/app/core/models/application.model.ts", + "type": "file", + "name": "application.model.ts", + "filePath": "src/app/core/models/application.model.ts", + "summary": "Central domain model file. Defines TypeScript types (ApplicationStatus, CompanyCategory, JobSource), interfaces (Application, InterviewStage, HistoryEntry, PaginatedResponse, FilterParams, ImportResult), and display-label constant arrays with STATUS_COLORS.", + "tags": ["model", "domain", "types", "typescript", "constants"] + }, + { + "id": "file:src/app/core/services/application.service.ts", + "type": "service", + "name": "application.service.ts", + "filePath": "src/app/core/services/application.service.ts", + "summary": "Injectable Angular service providing the full CRUD API for job applications. Maps raw API responses (Go Gin field names) to domain model types. Methods: list, get, create, update, delete, archive, restore, getHistory, restoreHistory, addStage, updateStage, removeStage, importCSV, exportCSV, getTemplate.", + "tags": ["service", "http", "api", "angular", "injectable", "typescript", "rxjs"] + }, + { + "id": "file:src/app/core/guards/unsaved-changes.guard.ts", + "type": "file", + "name": "unsaved-changes.guard.ts", + "filePath": "src/app/core/guards/unsaved-changes.guard.ts", + "summary": "CanDeactivate route guard that checks a component's isDirty signal. Prompts users via window.confirm before leaving a route with unsaved changes.", + "tags": ["guard", "routing", "angular", "typescript", "ux"] + }, + { + "id": "file:src/app/features/application-list/application-list.component.ts", + "type": "file", + "name": "application-list.component.ts", + "filePath": "src/app/features/application-list/application-list.component.ts", + "summary": "Feature component showing the paginated, filterable list of job applications. Manages filter state (status, category, source, archived), sort order, pagination, action menus (archive/restore/delete), and integrates CsvImport, CsvExport, and ConfirmDialog sub-components. Uses Angular signals throughout.", + "tags": ["component", "feature", "angular", "standalone", "signals", "pagination", "filtering", "typescript"] + }, + { + "id": "file:src/app/features/application-list/application-list.component.html", + "type": "file", + "name": "application-list.component.html", + "filePath": "src/app/features/application-list/application-list.component.html", + "summary": "Template for the application list. Contains filter bar, sortable table, action menus, pagination controls, and conditional CSV import panel.", + "tags": ["template", "html", "angular", "component"] + }, + { + "id": "file:src/app/features/application-detail/application-detail.component.ts", + "type": "file", + "name": "application-detail.component.ts", + "filePath": "src/app/features/application-detail/application-detail.component.ts", + "summary": "Feature component for creating or editing a single job application. Handles form state with snapshot-based dirty tracking, inline validation, interview stage CRUD, history panel toggle, archive/restore, and discard/delete confirmation dialogs. Implements HasUnsavedChanges for route guard integration.", + "tags": ["component", "feature", "angular", "standalone", "signals", "form", "validation", "typescript"] + }, + { + "id": "file:src/app/features/application-detail/application-detail.component.html", + "type": "file", + "name": "application-detail.component.html", + "filePath": "src/app/features/application-detail/application-detail.component.html", + "summary": "Template for the application detail/edit form. Renders all form fields, interview stage list with inline editing, history panel trigger, and conditional confirmation dialogs.", + "tags": ["template", "html", "angular", "component", "form"] + }, + { + "id": "file:src/app/features/history-panel/history-panel.component.ts", + "type": "file", + "name": "history-panel.component.ts", + "filePath": "src/app/features/history-panel/history-panel.component.ts", + "summary": "Slide-in history panel component. Loads and displays the audit history of a given application using applicationId @Input. Supports accordion expand/collapse for each entry's field changes, and a 'Restore to this point' action via ApplicationService.restoreHistory. Emits closed and restored events.", + "tags": ["component", "feature", "angular", "standalone", "history", "signals", "typescript"] + }, + { + "id": "file:src/app/features/csv/csv-import.component.ts", + "type": "file", + "name": "csv-import.component.ts", + "filePath": "src/app/features/csv/csv-import.component.ts", + "summary": "Standalone component for uploading and importing a CSV file of job applications. Shows import progress, result summary (imported/skipped/errors), and emits closeImport and importSuccess events.", + "tags": ["component", "feature", "angular", "standalone", "csv", "typescript"] + }, + { + "id": "file:src/app/features/csv/csv-export.component.ts", + "type": "file", + "name": "csv-export.component.ts", + "filePath": "src/app/features/csv/csv-export.component.ts", + "summary": "Minimal standalone component with two anchor links for downloading the CSV export and the CSV template from the API.", + "tags": ["component", "feature", "angular", "standalone", "csv", "typescript"] + }, + { + "id": "file:src/app/shared/components/confirm-dialog/confirm-dialog.component.ts", + "type": "file", + "name": "confirm-dialog.component.ts", + "filePath": "src/app/shared/components/confirm-dialog/confirm-dialog.component.ts", + "summary": "Reusable modal confirmation dialog. Accepts title, message, confirmLabel, confirmDanger @Inputs. Emits confirmed and cancelled @Outputs. Uses role='dialog' for accessible E2E selectors.", + "tags": ["component", "shared", "angular", "standalone", "modal", "typescript"] + }, + { + "id": "file:src/app/shared/components/header/header.component.ts", + "type": "file", + "name": "header.component.ts", + "filePath": "src/app/shared/components/header/header.component.ts", + "summary": "Application header component with brand link and dark/light mode toggle. Persists theme preference to localStorage and applies 'dark' class to document root.", + "tags": ["component", "shared", "angular", "standalone", "dark-mode", "typescript"] + }, + { + "id": "file:src/app/shared/components/inline-edit/inline-edit.component.ts", + "type": "file", + "name": "inline-edit.component.ts", + "filePath": "src/app/shared/components/inline-edit/inline-edit.component.ts", + "summary": "Reusable inline-edit component. Displays a value as text; on click switches to an input field. Saves on Enter/blur; cancels on Escape. Emits saved event with the trimmed new value.", + "tags": ["component", "shared", "angular", "standalone", "ux", "typescript"] + }, + { + "id": "file:src/app/shared/components/resizable-textarea/resizable-textarea.component.ts", + "type": "file", + "name": "resizable-textarea.component.ts", + "filePath": "src/app/shared/components/resizable-textarea/resizable-textarea.component.ts", + "summary": "Textarea component that auto-expands its height to fit content. Accepts value, id, placeholder, rows @Inputs. Emits valueChange. Uses ViewChild to access the native element for resize calculations.", + "tags": ["component", "shared", "angular", "standalone", "textarea", "typescript"] + }, + { + "id": "file:src/app/shared/pipes/relative-date.pipe.ts", + "type": "file", + "name": "relative-date.pipe.ts", + "filePath": "src/app/shared/pipes/relative-date.pipe.ts", + "summary": "Angular pipe that formats ISO date strings as human-readable relative time (e.g. '3 days ago', 'just now'). Used in history entries and application list timestamps.", + "tags": ["pipe", "shared", "angular", "standalone", "date", "typescript"] + }, + { + "id": "file:src/styles.css", + "type": "file", + "name": "styles.css", + "filePath": "src/styles.css", + "summary": "Global stylesheet. Imports Tailwind CSS base and utilities via @import 'tailwindcss'. Applied project-wide.", + "tags": ["css", "tailwind", "styles"] + }, + { + "id": "file:src/index.html", + "type": "file", + "name": "index.html", + "filePath": "src/index.html", + "summary": "HTML shell page. Mounts the Angular app via selector. References favicon, sets viewport meta.", + "tags": ["html", "entry-point", "angular"] + }, + { + "id": "file:Dockerfile", + "type": "config", + "name": "Dockerfile", + "filePath": "Dockerfile", + "summary": "Multi-stage Docker build. Stage 1: node:22-alpine builds the Angular app with npm run build. Stage 2: nginx:alpine serves the built browser bundle from /usr/share/nginx/html on port 3060.", + "tags": ["docker", "deployment", "nginx", "config"] + }, + { + "id": "file:nginx.conf", + "type": "config", + "name": "nginx.conf", + "filePath": "nginx.conf", + "summary": "nginx configuration for the containerized deployment. Serves the SPA on port 3060 with SPA fallback (try_files to index.html). Proxies /api/ requests to the go-api container on port 5070.", + "tags": ["nginx", "proxy", "deployment", "config"] + }, + { + "id": "file:tsconfig.json", + "type": "config", + "name": "tsconfig.json", + "filePath": "tsconfig.json", + "summary": "TypeScript compiler configuration. Targets ES2022 with strict mode, noImplicitOverride, strictTemplates (Angular). Uses 'bundler' module resolution. Enables experimentalDecorators.", + "tags": ["typescript", "config", "compiler"] + }, + { + "id": "file:package.json", + "type": "config", + "name": "package.json", + "filePath": "package.json", + "summary": "npm package manifest for angular-ui. Angular 21.2.x, RxJS 7.8.x, Tailwind CSS 4.x, Jest 30 with jest-preset-angular, Testing Library. Dev server on port 3060.", + "tags": ["config", "npm", "dependencies", "angular"] + } + ], + "edges": [ + { + "source": "file:src/main.ts", + "target": "file:src/app/app.config.ts", + "type": "imports", + "label": "imports appConfig" + }, + { + "source": "file:src/main.ts", + "target": "file:src/app/app.component.ts", + "type": "imports", + "label": "imports AppComponent" + }, + { + "source": "file:src/app/app.config.ts", + "target": "file:src/app/app.routes.ts", + "type": "imports", + "label": "imports routes" + }, + { + "source": "file:src/app/app.routes.ts", + "target": "file:src/app/core/guards/unsaved-changes.guard.ts", + "type": "imports", + "label": "imports unsavedChangesGuard" + }, + { + "source": "file:src/app/app.routes.ts", + "target": "file:src/app/features/application-list/application-list.component.ts", + "type": "lazy-loads", + "label": "lazy-loads at /" + }, + { + "source": "file:src/app/app.routes.ts", + "target": "file:src/app/features/application-detail/application-detail.component.ts", + "type": "lazy-loads", + "label": "lazy-loads at applications/:id" + }, + { + "source": "file:src/app/app.component.ts", + "target": "file:src/app/shared/components/header/header.component.ts", + "type": "imports", + "label": "imports HeaderComponent" + }, + { + "source": "file:src/app/features/application-list/application-list.component.ts", + "target": "file:src/app/core/services/application.service.ts", + "type": "imports", + "label": "injects ApplicationService" + }, + { + "source": "file:src/app/features/application-list/application-list.component.ts", + "target": "file:src/app/core/models/application.model.ts", + "type": "imports", + "label": "imports Application, FilterParams, constants" + }, + { + "source": "file:src/app/features/application-list/application-list.component.ts", + "target": "file:src/app/shared/pipes/relative-date.pipe.ts", + "type": "imports", + "label": "imports RelativeDatePipe" + }, + { + "source": "file:src/app/features/application-list/application-list.component.ts", + "target": "file:src/app/features/csv/csv-import.component.ts", + "type": "imports", + "label": "imports CsvImportComponent" + }, + { + "source": "file:src/app/features/application-list/application-list.component.ts", + "target": "file:src/app/features/csv/csv-export.component.ts", + "type": "imports", + "label": "imports CsvExportComponent" + }, + { + "source": "file:src/app/features/application-list/application-list.component.ts", + "target": "file:src/app/shared/components/confirm-dialog/confirm-dialog.component.ts", + "type": "imports", + "label": "imports ConfirmDialogComponent" + }, + { + "source": "file:src/app/features/application-list/application-list.component.ts", + "target": "file:src/app/features/application-list/application-list.component.html", + "type": "uses", + "label": "templateUrl" + }, + { + "source": "file:src/app/features/application-detail/application-detail.component.ts", + "target": "file:src/app/core/services/application.service.ts", + "type": "imports", + "label": "injects ApplicationService" + }, + { + "source": "file:src/app/features/application-detail/application-detail.component.ts", + "target": "file:src/app/core/models/application.model.ts", + "type": "imports", + "label": "imports Application, InterviewStage, constants" + }, + { + "source": "file:src/app/features/application-detail/application-detail.component.ts", + "target": "file:src/app/features/history-panel/history-panel.component.ts", + "type": "imports", + "label": "imports HistoryPanelComponent" + }, + { + "source": "file:src/app/features/application-detail/application-detail.component.ts", + "target": "file:src/app/shared/components/confirm-dialog/confirm-dialog.component.ts", + "type": "imports", + "label": "imports ConfirmDialogComponent" + }, + { + "source": "file:src/app/features/application-detail/application-detail.component.ts", + "target": "file:src/app/features/application-detail/application-detail.component.html", + "type": "uses", + "label": "templateUrl" + }, + { + "source": "file:src/app/features/history-panel/history-panel.component.ts", + "target": "file:src/app/core/services/application.service.ts", + "type": "imports", + "label": "injects ApplicationService" + }, + { + "source": "file:src/app/features/history-panel/history-panel.component.ts", + "target": "file:src/app/core/models/application.model.ts", + "type": "imports", + "label": "imports HistoryEntry" + }, + { + "source": "file:src/app/features/history-panel/history-panel.component.ts", + "target": "file:src/app/shared/pipes/relative-date.pipe.ts", + "type": "imports", + "label": "imports RelativeDatePipe" + }, + { + "source": "file:src/app/features/csv/csv-import.component.ts", + "target": "file:src/app/core/services/application.service.ts", + "type": "imports", + "label": "injects ApplicationService" + }, + { + "source": "file:src/app/features/csv/csv-import.component.ts", + "target": "file:src/app/core/models/application.model.ts", + "type": "imports", + "label": "imports ImportResult" + }, + { + "source": "file:src/app/core/services/application.service.ts", + "target": "file:src/app/core/models/application.model.ts", + "type": "imports", + "label": "imports Application, PaginatedResponse, etc." + }, + { + "source": "file:src/app/core/guards/unsaved-changes.guard.ts", + "target": "file:src/app/features/application-detail/application-detail.component.ts", + "type": "guards", + "label": "canDeactivate checks isDirty" + }, + { + "source": "file:Dockerfile", + "target": "file:nginx.conf", + "type": "uses", + "label": "COPYs nginx.conf into image" + }, + { + "source": "file:src/main.ts", + "target": "file:src/index.html", + "type": "uses", + "label": "bootstrapped into app-root" + }, + { + "source": "file:src/styles.css", + "target": "file:src/index.html", + "type": "uses", + "label": "global styles" + }, + { + "source": "file:tsconfig.json", + "target": "file:package.json", + "type": "uses", + "label": "TypeScript compiler config for Angular build" + } + ], + "layers": [ + { + "id": "layer:bootstrap", + "name": "Bootstrap & Configuration", + "description": "Entry point files that wire up the application: main.ts bootstraps Angular, app.config.ts provides global services (router, HttpClient, zone), app.routes.ts defines the routing table, and the shell app.component.ts holds the router outlet.", + "nodeIds": [ + "file:src/main.ts", + "file:src/app/app.config.ts", + "file:src/app/app.routes.ts", + "file:src/app/app.component.ts", + "file:src/index.html" + ] + }, + { + "id": "layer:domain", + "name": "Domain Model & Service", + "description": "Core business logic layer: application.model.ts defines all domain types and constants; application.service.ts is the single HTTP adapter that maps raw API responses to domain types and exposes Observable-based methods for all CRUD operations.", + "nodeIds": [ + "file:src/app/core/models/application.model.ts", + "file:src/app/core/services/application.service.ts", + "file:src/app/core/guards/unsaved-changes.guard.ts" + ] + }, + { + "id": "layer:features", + "name": "Feature Components", + "description": "Page-level smart components that compose the UI: ApplicationListComponent (paginated/filtered list), ApplicationDetailComponent (create/edit form with interview stages), HistoryPanelComponent (audit history slide-in), and CSV import/export components.", + "nodeIds": [ + "file:src/app/features/application-list/application-list.component.ts", + "file:src/app/features/application-list/application-list.component.html", + "file:src/app/features/application-detail/application-detail.component.ts", + "file:src/app/features/application-detail/application-detail.component.html", + "file:src/app/features/history-panel/history-panel.component.ts", + "file:src/app/features/csv/csv-import.component.ts", + "file:src/app/features/csv/csv-export.component.ts" + ] + }, + { + "id": "layer:shared", + "name": "Shared UI Primitives", + "description": "Reusable presentation components and pipes shared across features: ConfirmDialogComponent (modal), HeaderComponent (app header + dark mode), InlineEditComponent, ResizableTextareaComponent, and RelativeDatePipe.", + "nodeIds": [ + "file:src/app/shared/components/confirm-dialog/confirm-dialog.component.ts", + "file:src/app/shared/components/header/header.component.ts", + "file:src/app/shared/components/inline-edit/inline-edit.component.ts", + "file:src/app/shared/components/resizable-textarea/resizable-textarea.component.ts", + "file:src/app/shared/pipes/relative-date.pipe.ts" + ] + }, + { + "id": "layer:infrastructure", + "name": "Build & Deployment Infrastructure", + "description": "Configuration and infrastructure files: Dockerfile for multi-stage build, nginx.conf for SPA serving and API proxying, tsconfig.json for TypeScript compilation, and package.json for dependency management.", + "nodeIds": [ + "file:Dockerfile", + "file:nginx.conf", + "file:tsconfig.json", + "file:package.json", + "file:src/styles.css" + ] + } + ], + "tour": [ + { + "order": 1, + "title": "Entry Point: Bootstrap", + "description": "Start with src/main.ts — the single line that launches the Angular application using bootstrapApplication. It wires AppComponent to appConfig, establishing the standalone-components architecture (no NgModules).", + "nodeIds": ["file:src/main.ts"] + }, + { + "order": 2, + "title": "Application Configuration", + "description": "app.config.ts provides all root-level Angular services: Zone.js with event coalescing for efficient change detection, the Angular Router, and HttpClient. This is the DI root for the entire app.", + "nodeIds": ["file:src/app/app.config.ts"] + }, + { + "order": 3, + "title": "Routing", + "description": "app.routes.ts defines the route tree with lazy-loaded feature components. The list view loads at '/', the detail/create form at 'applications/:id' and 'applications/new', both protected by unsavedChangesGuard. A wildcard redirects all unknown paths to the list.", + "nodeIds": ["file:src/app/app.routes.ts", "file:src/app/core/guards/unsaved-changes.guard.ts"] + }, + { + "order": 4, + "title": "Domain Model", + "description": "application.model.ts is the single source of truth for all domain types. Study ApplicationStatus, CompanyCategory, JobSource union types; the Application and InterviewStage interfaces; PaginatedResponse, FilterParams, HistoryEntry; and the display-label constant arrays used throughout the UI.", + "nodeIds": ["file:src/app/core/models/application.model.ts"] + }, + { + "order": 5, + "title": "HTTP Service", + "description": "application.service.ts is the entire API layer. It maps raw Go Gin response shapes (stageName→name, stageOrder→order, diffs→changes) to clean domain types before returning Observables. All 14 methods go through this single service, making it the only file that 'knows' about the backend field names.", + "nodeIds": ["file:src/app/core/services/application.service.ts"] + }, + { + "order": 6, + "title": "Application List Feature", + "description": "ApplicationListComponent is the main screen. It uses Angular signals for all reactive state (applications, filters, pagination, loading, menu). Filter changes and sort clicks each call loadApplications(), which merges filter signal state into HttpParams. The three-dot action menu uses viewport-relative positioning to avoid layout clipping.", + "nodeIds": ["file:src/app/features/application-list/application-list.component.ts", "file:src/app/features/application-list/application-list.component.html"] + }, + { + "order": 7, + "title": "Application Detail Feature", + "description": "ApplicationDetailComponent handles both create ('applications/new') and edit ('applications/:id') flows. Dirty tracking uses JSON.stringify snapshot comparison. The form validates on submit with pure function validate(). Interview stages support full CRUD — locally buffered for new records, immediately persisted for existing ones.", + "nodeIds": ["file:src/app/features/application-detail/application-detail.component.ts", "file:src/app/features/application-detail/application-detail.component.html"] + }, + { + "order": 8, + "title": "History Panel", + "description": "HistoryPanelComponent is a slide-in panel that loads the audit trail for an application via getHistory(). Each entry shows field-level diffs (old/new values) in an accordion. Non-current entries have a 'Restore to this point' button that calls restoreHistory() and emits a restored event for the parent to reload.", + "nodeIds": ["file:src/app/features/history-panel/history-panel.component.ts"] + }, + { + "order": 9, + "title": "CSV Import & Export", + "description": "CsvImportComponent handles multipart file upload to /api/applications/import and shows import results (imported/skipped/errors). CsvExportComponent is a thin wrapper providing direct download links for the export endpoint and sample template — no JavaScript required for the download itself.", + "nodeIds": ["file:src/app/features/csv/csv-import.component.ts", "file:src/app/features/csv/csv-export.component.ts"] + }, + { + "order": 10, + "title": "Shared UI Primitives", + "description": "The shared layer provides four reusable building blocks: ConfirmDialogComponent (modal with danger variant), HeaderComponent (brand + dark mode toggle with localStorage persistence), InlineEditComponent (click-to-edit text), ResizableTextareaComponent (auto-expanding textarea), and RelativeDatePipe (human-readable relative timestamps).", + "nodeIds": [ + "file:src/app/shared/components/confirm-dialog/confirm-dialog.component.ts", + "file:src/app/shared/components/header/header.component.ts", + "file:src/app/shared/components/inline-edit/inline-edit.component.ts", + "file:src/app/shared/components/resizable-textarea/resizable-textarea.component.ts", + "file:src/app/shared/pipes/relative-date.pipe.ts" + ] + }, + { + "order": 11, + "title": "Build & Deployment", + "description": "The Dockerfile uses a two-stage build: Node 22 Alpine compiles the Angular app with ng build, then the static browser bundle is served by nginx:alpine. The nginx.conf provides SPA fallback routing and proxies /api/ to the go-api container on port 5070.", + "nodeIds": ["file:Dockerfile", "file:nginx.conf"] + } + ] +} diff --git a/angular-ui/.understand-anything/meta.json b/angular-ui/.understand-anything/meta.json new file mode 100644 index 00000000..01b6631b --- /dev/null +++ b/angular-ui/.understand-anything/meta.json @@ -0,0 +1,36 @@ +{ + "version": "2.5.1", + "projectName": "angular-ui", + "description": "Angular 21 standalone-component UI for a job application tracker. Pairs with go-api (Go Gin + pgx/sqlc) on port 5070 via dev proxy and nginx in production. Runs on port 3060.", + "gitHash": "e5054096efec8165e7d4cfe11fc103f77ad2d98f", + "analyzedAt": "2026-05-04T00:00:00.000Z", + "entryPoint": "src/main.ts", + "languages": [ + "TypeScript", + "HTML", + "CSS" + ], + "frameworks": [ + "Angular 21", + "RxJS 7", + "Tailwind CSS 4" + ], + "totalFiles": 25, + "stats": { + "totalNodes": 25, + "totalEdges": 30, + "totalLayers": 5, + "tourSteps": 11, + "nodeTypes": { + "file": 20, + "service": 1, + "config": 4 + }, + "edgeTypes": { + "imports": 21, + "lazy-loads": 2, + "uses": 6, + "guards": 1 + } + } +} diff --git a/api/.understand-anything/.understandignore b/api/.understand-anything/.understandignore new file mode 100644 index 00000000..ca37b98b --- /dev/null +++ b/api/.understand-anything/.understandignore @@ -0,0 +1,5 @@ +# .understandignore — patterns for files/dirs to exclude from analysis +# Built-in defaults (always excluded): node_modules/, .git/, dist/, build/, *.lock + +# Exclude generated Prisma client +node_modules/ diff --git a/api/.understand-anything/knowledge-graph.json b/api/.understand-anything/knowledge-graph.json new file mode 100644 index 00000000..c1284d43 --- /dev/null +++ b/api/.understand-anything/knowledge-graph.json @@ -0,0 +1,857 @@ +{ + "version": "1.0.0", + "project": { + "name": "application-tracker-api", + "languages": [ + "TypeScript", + "JavaScript", + "SQL" + ], + "frameworks": [ + "Express", + "Prisma", + "Zod", + "Jest" + ], + "description": "Express REST API for Application Tracker with Prisma ORM and PostgreSQL. Provides CRUD operations for job applications, interview stages, and application history with snapshot-based restore capabilities.", + "analyzedAt": "2026-05-04T00:00:00.000Z", + "gitCommitHash": "e5054096efec8165e7d4cfe11fc103f77ad2d98f" + }, + "nodes": [ + { + "id": "file:src/index.ts", + "type": "file", + "name": "src/index.ts", + "filePath": "src/index.ts", + "summary": "Application entry point. Bootstraps the Express server on port 5000, registers CORS, JSON body parsing, request logger middleware, the /applications router, and the global error handler. Also exposes a /health endpoint.", + "tags": [ + "entry-point", + "express", + "server", + "bootstrap" + ] + }, + { + "id": "file:src/types/index.ts", + "type": "file", + "name": "src/types/index.ts", + "filePath": "src/types/index.ts", + "summary": "Central Zod schema definitions and TypeScript type exports for the API. Defines ApplicationStatus, CompanyCategory, and JobSource enums, plus request DTOs for creating/updating applications and interview stages, list query parameters, and history/restore schemas.", + "tags": [ + "types", + "zod", + "validation", + "dto", + "schemas" + ] + }, + { + "id": "file:src/db/client.ts", + "type": "file", + "name": "src/db/client.ts", + "filePath": "src/db/client.ts", + "summary": "Initializes and exports the singleton Prisma client configured with the PrismaPg adapter (pg Pool). Reads DATABASE_URL from environment, targeting the express_prisma schema. Uses global singleton pattern to prevent connection pool exhaustion in hot-reload dev mode.", + "tags": [ + "database", + "prisma", + "singleton", + "postgresql", + "connection" + ] + }, + { + "id": "file:src/db/seed.ts", + "type": "file", + "name": "src/db/seed.ts", + "filePath": "src/db/seed.ts", + "summary": "Database seeding script. Clears existing applications and stages, then inserts two sample applications (Acme Corp, Tech Startup) with associated interview stages for development/testing.", + "tags": [ + "seed", + "database", + "development", + "script" + ] + }, + { + "id": "file:src/lib/logger.ts", + "type": "file", + "name": "src/lib/logger.ts", + "filePath": "src/lib/logger.ts", + "summary": "Minimal logger utility wrapping console.log/warn/error with info/warn/error methods. Provides a consistent logger interface across the codebase.", + "tags": [ + "logger", + "utility", + "logging" + ] + }, + { + "id": "file:src/middleware/errorHandler.ts", + "type": "file", + "name": "src/middleware/errorHandler.ts", + "filePath": "src/middleware/errorHandler.ts", + "summary": "Global Express error-handling middleware and AppError class. Handles AppError (domain errors with HTTP status codes), ZodError (validation failures returning 400 with field-level details), and generic 500 internal errors. Exports ErrorResponse interface.", + "tags": [ + "middleware", + "error-handling", + "express", + "validation" + ] + }, + { + "id": "file:src/middleware/logger.ts", + "type": "file", + "name": "src/middleware/logger.ts", + "filePath": "src/middleware/logger.ts", + "summary": "Express request logger middleware. Attaches a finish listener to track request duration and logs method, path, status code, and elapsed milliseconds for each completed request.", + "tags": [ + "middleware", + "logging", + "request", + "express" + ] + }, + { + "id": "file:src/routes/applications.ts", + "type": "file", + "name": "src/routes/applications.ts", + "filePath": "src/routes/applications.ts", + "summary": "Express router for the /applications resource. Defines CRUD routes (GET list, POST create, GET by id, PATCH update, DELETE), archive/restore actions, history listing, and history restore endpoints. Validates requests using Zod schemas and delegates to applicationService and historyService.", + "tags": [ + "routes", + "express", + "applications", + "rest-api", + "crud" + ] + }, + { + "id": "file:src/routes/interview-stages.ts", + "type": "file", + "name": "src/routes/interview-stages.ts", + "filePath": "src/routes/interview-stages.ts", + "summary": "Express router (mergeParams) for interview stages nested under /applications/:id/interview-stages. Provides POST (create), PATCH (update by stageId), and DELETE endpoints. Delegates to interviewStageService.", + "tags": [ + "routes", + "express", + "interview-stages", + "rest-api", + "nested" + ] + }, + { + "id": "file:src/services/applications.service.ts", + "type": "file", + "name": "src/services/applications.service.ts", + "filePath": "src/services/applications.service.ts", + "summary": "ApplicationService class encapsulating business logic for job application CRUD. Handles paginated list with multi-value filters, date string to ISO datetime conversion, unsubmitted-status/dateApplied coupling logic, archive/restore, and triggers history recording on mutations.", + "tags": [ + "service", + "business-logic", + "applications", + "prisma", + "pagination" + ] + }, + { + "id": "file:src/services/history.service.ts", + "type": "file", + "name": "src/services/history.service.ts", + "filePath": "src/services/history.service.ts", + "summary": "History service implementing snapshot-based audit trail. Records full application snapshots on each mutation, computes field-level diffs between snapshots for the history list, and supports transactional restore to any prior version by replaying a snapshot. Exports FIELD_LABELS, recordHistory, listHistory, restoreToVersion, computeFieldDiffs, and buildDescription.", + "tags": [ + "service", + "history", + "audit-trail", + "snapshot", + "restore", + "prisma" + ] + }, + { + "id": "file:src/services/stages.service.ts", + "type": "file", + "name": "src/services/stages.service.ts", + "filePath": "src/services/stages.service.ts", + "summary": "InterviewStageService class managing interview stage lifecycle. Auto-increments stage order, validates parent application existence, handles date string conversion, and records history events (stage_add, stage_update, stage_delete) for each mutation.", + "tags": [ + "service", + "interview-stages", + "business-logic", + "prisma", + "ordering" + ] + }, + { + "id": "file:prisma/schema.prisma", + "type": "schema", + "name": "prisma/schema.prisma", + "filePath": "prisma/schema.prisma", + "summary": "Prisma schema defining three models in the express_prisma PostgreSQL schema: Application (job applications with status, salary, archive flags), ApplicationHistory (snapshot-based audit trail with JSON snapshot column), and InterviewStage (ordered interview rounds linked to applications). Includes legacy enums for ApplicationStatus, CompanyCategory, and JobSource.", + "tags": [ + "prisma", + "schema", + "database", + "models", + "postgresql" + ] + }, + { + "id": "file:prisma/migrations/0_init/migration.sql", + "type": "file", + "name": "prisma/migrations/0_init/migration.sql", + "filePath": "prisma/migrations/0_init/migration.sql", + "summary": "Initial database migration creating the express_prisma schema, Application, ApplicationHistory, and InterviewStage tables with their indexes.", + "tags": [ + "migration", + "sql", + "database", + "initial" + ] + }, + { + "id": "file:prisma/migrations/20260122202547_add_schema_prefix/migration.sql", + "type": "file", + "name": "prisma/migrations/20260122202547_add_schema_prefix/migration.sql", + "filePath": "prisma/migrations/20260122202547_add_schema_prefix/migration.sql", + "summary": "Migration adding schema prefix to existing tables/indexes to align with multi-schema PostgreSQL setup.", + "tags": [ + "migration", + "sql", + "database", + "schema" + ] + }, + { + "id": "file:tests/contract/applications.contract.test.ts", + "type": "file", + "name": "tests/contract/applications.contract.test.ts", + "filePath": "tests/contract/applications.contract.test.ts", + "summary": "Contract tests for the /applications endpoints verifying response shape (paginated list with items/page/limit/total) and HTTP status codes for list, create, and validation failure scenarios.", + "tags": [ + "test", + "contract", + "applications", + "jest" + ] + }, + { + "id": "file:tests/contract/health.contract.test.ts", + "type": "file", + "name": "tests/contract/health.contract.test.ts", + "filePath": "tests/contract/health.contract.test.ts", + "summary": "Contract test for the /health endpoint verifying 200 status and {status: 'ok'} response body.", + "tags": [ + "test", + "contract", + "health", + "jest" + ] + }, + { + "id": "file:tests/contract/stages.contract.test.ts", + "type": "file", + "name": "tests/contract/stages.contract.test.ts", + "filePath": "tests/contract/stages.contract.test.ts", + "summary": "Contract tests for interview stage endpoints (POST create, PATCH update) verifying status codes. Tests use placeholder assertions pending full route implementation.", + "tags": [ + "test", + "contract", + "interview-stages", + "jest" + ] + }, + { + "id": "file:tests/integration/applications.integration.test.ts", + "type": "file", + "name": "tests/integration/applications.integration.test.ts", + "filePath": "tests/integration/applications.integration.test.ts", + "summary": "Integration test placeholder for the full application workflow. Currently a stub with a TODO for Prisma-backed setup.", + "tags": [ + "test", + "integration", + "placeholder", + "jest" + ] + }, + { + "id": "file:tests/integration/stages.integration.test.ts", + "type": "file", + "name": "tests/integration/stages.integration.test.ts", + "filePath": "tests/integration/stages.integration.test.ts", + "summary": "Integration test placeholder for interview stages workflow.", + "tags": [ + "test", + "integration", + "placeholder", + "jest" + ] + }, + { + "id": "file:tests/unit/applications.service.test.ts", + "type": "file", + "name": "tests/unit/applications.service.test.ts", + "filePath": "tests/unit/applications.service.test.ts", + "summary": "Unit tests for ApplicationService verifying the unsubmitted-status/dateApplied coupling logic. Tests createApplication (defaults to unsubmitted with null dateApplied) and updateApplication (clears dateApplied when status switches to unsubmitted, preserves it otherwise). Uses Jest mocks for Prisma client and history service.", + "tags": [ + "test", + "unit", + "applications-service", + "jest", + "mock" + ] + }, + { + "id": "file:tests/utils/server.ts", + "type": "file", + "name": "tests/utils/server.ts", + "filePath": "tests/utils/server.ts", + "summary": "Test utility creating a lightweight Express app with stub routes (GET /health, GET /applications, POST /applications) for contract testing without a real database. Exports createTestApp() and getRequest() helpers.", + "tags": [ + "test", + "utility", + "express", + "stub", + "supertest" + ] + }, + { + "id": "file:Dockerfile", + "type": "file", + "name": "Dockerfile", + "filePath": "Dockerfile", + "summary": "Multi-stage Docker build: deps stage installs npm packages, builder stage compiles TypeScript and generates Prisma client, runner stage creates a minimal production image running Prisma migrations then seeding before starting the Node.js server on port 5000.", + "tags": [ + "docker", + "infrastructure", + "multi-stage", + "deployment" + ] + }, + { + "id": "file:scripts/load-test.k6.js", + "type": "file", + "name": "scripts/load-test.k6.js", + "filePath": "scripts/load-test.k6.js", + "summary": "k6 load test script exercising the health check, list applications, and create application endpoints. Configured with ramp-up/sustain/ramp-down stages at 10 VUs, with p95 GET ≤200ms, p95 POST ≤500ms, p99 ≤1000ms, and error rate <1% thresholds.", + "tags": [ + "performance", + "load-test", + "k6", + "script" + ] + }, + { + "id": "file:scripts/local-setup.sh", + "type": "file", + "name": "scripts/local-setup.sh", + "filePath": "scripts/local-setup.sh", + "summary": "Shell script for local development environment setup.", + "tags": [ + "script", + "setup", + "development" + ] + }, + { + "id": "file:jest.config.ts", + "type": "config", + "name": "jest.config.ts", + "filePath": "jest.config.ts", + "summary": "Jest configuration using ts-jest preset. Matches all tests/**.test.ts files, maps .js imports back to source for ESM compatibility, and enforces 50% coverage thresholds on branches/functions/lines/statements.", + "tags": [ + "config", + "jest", + "testing", + "coverage" + ] + }, + { + "id": "file:tsconfig.json", + "type": "config", + "name": "tsconfig.json", + "filePath": "tsconfig.json", + "summary": "TypeScript configuration targeting ES2020 with strict mode, esModuleInterop, and declaration maps. Compiles src/ to dist/, excludes node_modules and tests.", + "tags": [ + "config", + "typescript", + "build" + ] + }, + { + "id": "file:eslint.config.js", + "type": "config", + "name": "eslint.config.js", + "filePath": "eslint.config.js", + "summary": "ESLint flat config using TypeScript-ESLint for linting src/ and tests/.", + "tags": [ + "config", + "eslint", + "linting" + ] + }, + { + "id": "file:package.json", + "type": "config", + "name": "package.json", + "filePath": "package.json", + "summary": "NPM package manifest for the Express API. Defines scripts for dev (tsx watch), build (tsc), test (jest), lint (eslint), Prisma commands, seeding, and k6 performance testing. Lists Express 5, Prisma 7, Zod 4, pg as runtime deps and Jest, ts-jest, supertest as dev deps.", + "tags": [ + "config", + "npm", + "manifest", + "dependencies" + ] + }, + { + "id": "file:prisma.config.ts", + "type": "config", + "name": "prisma.config.ts", + "filePath": "prisma.config.ts", + "summary": "Prisma configuration file pointing to the schema and migration directories.", + "tags": [ + "config", + "prisma", + "database" + ] + } + ], + "edges": [ + { + "source": "file:src/index.ts", + "target": "file:src/middleware/logger.ts", + "type": "imports", + "label": "uses requestLogger middleware" + }, + { + "source": "file:src/index.ts", + "target": "file:src/lib/logger.ts", + "type": "imports", + "label": "uses logger for server startup" + }, + { + "source": "file:src/index.ts", + "target": "file:src/middleware/errorHandler.ts", + "type": "imports", + "label": "registers global error handler" + }, + { + "source": "file:src/index.ts", + "target": "file:src/routes/applications.ts", + "type": "imports", + "label": "mounts /applications router" + }, + { + "source": "file:src/routes/applications.ts", + "target": "file:src/services/applications.service.ts", + "type": "imports", + "label": "delegates CRUD to applicationService" + }, + { + "source": "file:src/routes/applications.ts", + "target": "file:src/services/history.service.ts", + "type": "imports", + "label": "calls listHistory and restoreToVersion" + }, + { + "source": "file:src/routes/applications.ts", + "target": "file:src/routes/interview-stages.ts", + "type": "imports", + "label": "mounts nested interview stages router" + }, + { + "source": "file:src/routes/applications.ts", + "target": "file:src/types/index.ts", + "type": "imports", + "label": "validates with Zod schemas" + }, + { + "source": "file:src/routes/interview-stages.ts", + "target": "file:src/services/stages.service.ts", + "type": "imports", + "label": "delegates to interviewStageService" + }, + { + "source": "file:src/routes/interview-stages.ts", + "target": "file:src/types/index.ts", + "type": "imports", + "label": "validates with Zod schemas" + }, + { + "source": "file:src/services/applications.service.ts", + "target": "file:src/db/client.ts", + "type": "imports", + "label": "uses prisma client for DB access" + }, + { + "source": "file:src/services/applications.service.ts", + "target": "file:src/types/index.ts", + "type": "imports", + "label": "uses input type definitions" + }, + { + "source": "file:src/services/applications.service.ts", + "target": "file:src/middleware/errorHandler.ts", + "type": "imports", + "label": "throws AppError for not-found" + }, + { + "source": "file:src/services/applications.service.ts", + "target": "file:src/services/history.service.ts", + "type": "imports", + "label": "records history after mutations" + }, + { + "source": "file:src/services/history.service.ts", + "target": "file:src/db/client.ts", + "type": "imports", + "label": "uses prisma client for snapshot storage" + }, + { + "source": "file:src/services/stages.service.ts", + "target": "file:src/db/client.ts", + "type": "imports", + "label": "uses prisma client for stage CRUD" + }, + { + "source": "file:src/services/stages.service.ts", + "target": "file:src/types/index.ts", + "type": "imports", + "label": "uses input type definitions" + }, + { + "source": "file:src/services/stages.service.ts", + "target": "file:src/middleware/errorHandler.ts", + "type": "imports", + "label": "throws AppError for not-found" + }, + { + "source": "file:src/services/stages.service.ts", + "target": "file:src/services/history.service.ts", + "type": "imports", + "label": "records stage history events" + }, + { + "source": "file:src/middleware/errorHandler.ts", + "target": "file:src/lib/logger.ts", + "type": "imports", + "label": "logs errors" + }, + { + "source": "file:src/middleware/logger.ts", + "target": "file:src/lib/logger.ts", + "type": "imports", + "label": "uses logger for request logging" + }, + { + "source": "file:src/db/seed.ts", + "target": "file:src/db/client.ts", + "type": "imports", + "label": "seeds via prisma client" + }, + { + "source": "file:src/db/seed.ts", + "target": "file:src/lib/logger.ts", + "type": "imports", + "label": "logs seed progress" + }, + { + "source": "file:tests/contract/applications.contract.test.ts", + "target": "file:tests/utils/server.ts", + "type": "imports", + "label": "uses test app and request helper" + }, + { + "source": "file:tests/contract/health.contract.test.ts", + "target": "file:tests/utils/server.ts", + "type": "imports", + "label": "uses test app and request helper" + }, + { + "source": "file:tests/contract/stages.contract.test.ts", + "target": "file:tests/utils/server.ts", + "type": "imports", + "label": "uses test app and request helper" + }, + { + "source": "file:tests/unit/applications.service.test.ts", + "target": "file:src/services/applications.service.ts", + "type": "imports", + "label": "tests ApplicationService" + }, + { + "source": "file:tests/unit/applications.service.test.ts", + "target": "file:src/db/client.ts", + "type": "imports", + "label": "mocks prisma client" + }, + { + "source": "file:tests/utils/server.ts", + "target": "file:src/middleware/logger.ts", + "type": "imports", + "label": "uses requestLogger in test app" + }, + { + "source": "file:tests/utils/server.ts", + "target": "file:src/middleware/errorHandler.ts", + "type": "imports", + "label": "uses errorHandler in test app" + }, + { + "source": "file:prisma/schema.prisma", + "target": "file:src/db/client.ts", + "type": "defines", + "label": "schema defines models consumed by client" + }, + { + "source": "file:prisma/migrations/0_init/migration.sql", + "target": "file:prisma/schema.prisma", + "type": "implements", + "label": "initial migration implements schema" + }, + { + "source": "file:prisma/migrations/20260122202547_add_schema_prefix/migration.sql", + "target": "file:prisma/schema.prisma", + "type": "implements", + "label": "schema prefix migration" + }, + { + "source": "file:Dockerfile", + "target": "file:src/index.ts", + "type": "references", + "label": "builds and runs the app" + }, + { + "source": "file:Dockerfile", + "target": "file:prisma/schema.prisma", + "type": "references", + "label": "generates Prisma client and runs migrations" + }, + { + "source": "file:scripts/load-test.k6.js", + "target": "file:src/index.ts", + "type": "references", + "label": "exercises live API endpoints" + }, + { + "source": "file:jest.config.ts", + "target": "file:src/services/applications.service.ts", + "type": "references", + "label": "coverage collection target" + }, + { + "source": "file:package.json", + "target": "file:src/index.ts", + "type": "references", + "label": "main entry point" + }, + { + "source": "file:package.json", + "target": "file:jest.config.ts", + "type": "references", + "label": "test script uses jest config" + }, + { + "source": "file:tsconfig.json", + "target": "file:src/index.ts", + "type": "references", + "label": "TypeScript compilation root for src/" + }, + { + "source": "file:eslint.config.js", + "target": "file:src/index.ts", + "type": "references", + "label": "lint target src/ and tests/" + }, + { + "source": "file:prisma.config.ts", + "target": "file:prisma/schema.prisma", + "type": "references", + "label": "points to prisma schema" + }, + { + "source": "file:tests/integration/applications.integration.test.ts", + "target": "file:src/services/applications.service.ts", + "type": "references", + "label": "integration placeholder for application service" + }, + { + "source": "file:tests/integration/stages.integration.test.ts", + "target": "file:src/services/stages.service.ts", + "type": "references", + "label": "integration placeholder for stages service" + }, + { + "source": "file:scripts/local-setup.sh", + "target": "file:src/index.ts", + "type": "references", + "label": "sets up local environment for running the API" + } + ], + "layers": [ + { + "id": "layer:entry-and-infrastructure", + "name": "Entry & Infrastructure", + "description": "Application bootstrap, Docker packaging, database migrations, and tooling configuration files.", + "nodeIds": [ + "file:src/index.ts", + "file:Dockerfile", + "file:prisma/migrations/0_init/migration.sql", + "file:prisma/migrations/20260122202547_add_schema_prefix/migration.sql", + "file:scripts/local-setup.sh", + "file:package.json", + "file:tsconfig.json", + "file:eslint.config.js", + "file:jest.config.ts", + "file:prisma.config.ts" + ] + }, + { + "id": "layer:data-and-schema", + "name": "Data & Schema", + "description": "Prisma schema definition, database client initialization, and the seed script for populating development data.", + "nodeIds": [ + "file:prisma/schema.prisma", + "file:src/db/client.ts", + "file:src/db/seed.ts" + ] + }, + { + "id": "layer:types-and-validation", + "name": "Types & Validation", + "description": "Centralized Zod schemas and TypeScript type exports used by routes and services for input validation and type safety.", + "nodeIds": [ + "file:src/types/index.ts" + ] + }, + { + "id": "layer:utilities-and-middleware", + "name": "Utilities & Middleware", + "description": "Shared utilities (logger) and Express middleware (request logger, error handler) used across the application.", + "nodeIds": [ + "file:src/lib/logger.ts", + "file:src/middleware/errorHandler.ts", + "file:src/middleware/logger.ts" + ] + }, + { + "id": "layer:services", + "name": "Service Layer", + "description": "Business logic layer with ApplicationService, InterviewStageService, and HistoryService encapsulating all data access and domain rules.", + "nodeIds": [ + "file:src/services/applications.service.ts", + "file:src/services/history.service.ts", + "file:src/services/stages.service.ts" + ] + }, + { + "id": "layer:routes", + "name": "Route Layer", + "description": "Express routers translating HTTP requests into service calls. Applications router handles the main resource; interview-stages router is nested under applications.", + "nodeIds": [ + "file:src/routes/applications.ts", + "file:src/routes/interview-stages.ts" + ] + }, + { + "id": "layer:tests-and-performance", + "name": "Tests & Performance", + "description": "All test suites (contract, integration, unit) plus the k6 load test script and test utilities for building stub Express apps.", + "nodeIds": [ + "file:tests/contract/applications.contract.test.ts", + "file:tests/contract/health.contract.test.ts", + "file:tests/contract/stages.contract.test.ts", + "file:tests/integration/applications.integration.test.ts", + "file:tests/integration/stages.integration.test.ts", + "file:tests/unit/applications.service.test.ts", + "file:tests/utils/server.ts", + "file:scripts/load-test.k6.js" + ] + } + ], + "tour": [ + { + "order": 1, + "title": "Entry Point: Server Bootstrap", + "description": "Start here to understand how the API server is assembled. src/index.ts initialises Express, registers middleware in the correct order (CORS, JSON, requestLogger), mounts the /applications router, and attaches the error handler last.", + "nodeIds": [ + "file:src/index.ts" + ] + }, + { + "order": 2, + "title": "Data Schema: Prisma Models", + "description": "The Prisma schema is the source of truth for the database structure. Three models — Application, ApplicationHistory, and InterviewStage — live in the express_prisma PostgreSQL schema. ApplicationHistory stores full JSON snapshots enabling version restore.", + "nodeIds": [ + "file:prisma/schema.prisma" + ] + }, + { + "order": 3, + "title": "Types & Validation: Zod Schemas", + "description": "All request validation is defined in src/types/index.ts using Zod. The schemas enforce status enum values, salary ranges, URL formats, and date strings. UpdateApplicationSchema extends CreateApplicationSchema with archive/offer fields.", + "nodeIds": [ + "file:src/types/index.ts" + ] + }, + { + "order": 4, + "title": "Database Client: Prisma + pg Pool", + "description": "src/db/client.ts configures the Prisma client with the PrismaPg adapter backed by a pg connection pool. The global singleton pattern prevents multiple pool instances during tsx hot-reload in development.", + "nodeIds": [ + "file:src/db/client.ts" + ] + }, + { + "order": 5, + "title": "Core Service: Application Business Logic", + "description": "ApplicationService is the heart of the API. It handles paginated filtering with comma-separated multi-value params, enforces the unsubmitted-status/dateApplied coupling (null dateApplied when status is unsubmitted), and triggers history recording after every mutation.", + "nodeIds": [ + "file:src/services/applications.service.ts" + ] + }, + { + "order": 6, + "title": "History Service: Snapshot & Restore", + "description": "history.service.ts implements a snapshot-based audit trail. After each mutation, captureSnapshot serializes the full application state to JSON. listHistory computes field-level diffs between adjacent snapshots. restoreToVersion wraps the restore in a database transaction to atomically replace application fields and interview stages.", + "nodeIds": [ + "file:src/services/history.service.ts" + ] + }, + { + "order": 7, + "title": "Interview Stages Service", + "description": "InterviewStageService manages ordered interview rounds. It auto-assigns the next order value, verifies parent application existence, converts date strings, and records history events for stage additions, updates, and deletions.", + "nodeIds": [ + "file:src/services/stages.service.ts" + ] + }, + { + "order": 8, + "title": "Routes: Applications & Interview Stages", + "description": "The applications router exposes the full REST surface: list with filters, CRUD, archive/restore, history listing, and history restore. The interview-stages router is nested using mergeParams so it inherits the :id parameter from the parent.", + "nodeIds": [ + "file:src/routes/applications.ts", + "file:src/routes/interview-stages.ts" + ] + }, + { + "order": 9, + "title": "Error Handling & Middleware", + "description": "AppError carries an HTTP status code and optional field-level details. The errorHandler translates AppError and ZodError to structured JSON responses; all other errors become generic 500s. The requestLogger middleware measures and logs per-request duration.", + "nodeIds": [ + "file:src/middleware/errorHandler.ts", + "file:src/middleware/logger.ts" + ] + }, + { + "order": 10, + "title": "Tests: Contract, Unit & Performance", + "description": "Contract tests use a stub Express app (tests/utils/server.ts) to verify response shapes without a database. Unit tests mock Prisma and the history service to isolate ApplicationService business logic. The k6 load test exercises the live API with performance thresholds.", + "nodeIds": [ + "file:tests/utils/server.ts", + "file:tests/unit/applications.service.test.ts", + "file:scripts/load-test.k6.js" + ] + } + ] +} \ No newline at end of file diff --git a/api/.understand-anything/meta.json b/api/.understand-anything/meta.json new file mode 100644 index 00000000..c896ea2c --- /dev/null +++ b/api/.understand-anything/meta.json @@ -0,0 +1,6 @@ +{ + "lastAnalyzedAt": "2026-05-04T00:00:00.000Z", + "gitCommitHash": "e5054096efec8165e7d4cfe11fc103f77ad2d98f", + "version": "1.0.0", + "analyzedFiles": 30 +} diff --git a/cspell.config.yaml b/cspell.config.yaml index ada739ad..abf4a52a 100644 --- a/cspell.config.yaml +++ b/cspell.config.yaml @@ -66,6 +66,13 @@ words: - Unsubmitted - uvx - VARCHAR + - creds + - EBADPLATFORM + - libexec + - localstack + - mktemp + - shutil + - subdirs - venv - Vinxi - vitest diff --git a/fastapi/.understand-anything/.understandignore b/fastapi/.understand-anything/.understandignore new file mode 100644 index 00000000..e2ffe876 --- /dev/null +++ b/fastapi/.understand-anything/.understandignore @@ -0,0 +1,7 @@ +# .understandignore — patterns for files/dirs to exclude from analysis +# Built-in defaults (always excluded): node_modules/, .git/, dist/, build/, *.lock + +# Exclude Python virtual env and cache +.venv/ +__pycache__/ +*.pyc diff --git a/fastapi/.understand-anything/knowledge-graph.json b/fastapi/.understand-anything/knowledge-graph.json new file mode 100644 index 00000000..83aa840f --- /dev/null +++ b/fastapi/.understand-anything/knowledge-graph.json @@ -0,0 +1,488 @@ +{ + "nodes": [ + { + "id": "file:src/main.py", + "type": "file", + "name": "main.py", + "filePath": "src/main.py", + "summary": "FastAPI application factory. Creates the FastAPI instance, configures CORS middleware for all known frontend ports, registers global exception handlers (422 validation and 500 internal), and mounts the health and applications routers. Also defines the lifespan context manager that creates and closes the asyncpg connection pool.", + "tags": ["entry-point", "config", "middleware", "api-handler"] + }, + { + "id": "file:src/__main__.py", + "type": "file", + "name": "__main__.py", + "filePath": "src/__main__.py", + "summary": "Package entry point for `python -m src`. Reads the port from config and launches uvicorn with optional hot-reload controlled by the FASTAPI_RELOAD environment variable.", + "tags": ["entry-point", "config"] + }, + { + "id": "file:src/config.py", + "type": "file", + "name": "config.py", + "filePath": "src/config.py", + "summary": "Application configuration module. Loads the fastapi/.env file explicitly (avoiding root .env pickup) and exposes get_database_url() and get_api_port() helpers. Default port is 5160; default DB is the local Docker PostgreSQL.", + "tags": ["config"] + }, + { + "id": "file:src/db.py", + "type": "file", + "name": "db.py", + "filePath": "src/db.py", + "summary": "asyncpg connection pool management. Maintains a module-level pool singleton. create_pool() sets search_path=python_fastapi and conditionally enables SSL. get_pool() provides a runtime guard that raises if called before initialisation. close_pool() is called in the lifespan teardown.", + "tags": ["config", "data-model", "service"] + }, + { + "id": "file:src/enums.py", + "type": "file", + "name": "enums.py", + "filePath": "src/enums.py", + "summary": "Python StrEnum definitions mirroring the PostgreSQL custom types: ApplicationStatus (8 values), CompanyCategory (19 values), and JobSource (7 values). StrEnum makes values directly usable as strings in SQL casts.", + "tags": ["type-definition", "data-model"] + }, + { + "id": "file:src/schemas.py", + "type": "file", + "name": "schemas.py", + "filePath": "src/schemas.py", + "summary": "All Pydantic v2 request and response schemas. CamelModel base class uses alias_generator=to_camel so the API surface is camelCase while Python code stays snake_case. Defines ApplicationResponse, CreateApplicationRequest, UpdateApplicationRequest, InterviewStageResponse, CreateInterviewStageRequest, UpdateInterviewStageRequest, PaginatedApplicationsResponse, HistoryEntryResponse, PaginatedHistoryResponse, RestoreRequest, CsvRow (with BeforeValidator coercions for CSV strings), ImportResult, and error types.", + "tags": ["type-definition", "serialization"] + }, + { + "id": "file:src/routes/applications.py", + "type": "file", + "name": "routes/applications.py", + "filePath": "src/routes/applications.py", + "summary": "APIRouter for all /applications endpoints. Handles listing (with filtering, sorting, pagination), CRUD for applications, archive/restore, CSV import/export, interview stage CRUD, and history listing with restore. Delegates all business logic to service modules and returns model_dump(by_alias=True) responses.", + "tags": ["api-handler", "routing"] + }, + { + "id": "file:src/routes/health.py", + "type": "file", + "name": "routes/health.py", + "filePath": "src/routes/health.py", + "summary": "Minimal health check router mounted at /health. Returns {status: ok} for readiness/liveness probes.", + "tags": ["api-handler", "routing"] + }, + { + "id": "file:src/services/application.py", + "type": "file", + "name": "services/application.py", + "filePath": "src/services/application.py", + "summary": "Core application service. Implements list_applications (with dynamic WHERE/ORDER SQL, pagination), get_application, create_application (with status-based date_applied defaulting), update_application (partial PATCH using model_fields_set to build SET clauses, enum casting), delete_application, archive_application, and restore_application. Each mutation calls record_history after the DB write.", + "tags": ["service", "data-model"] + }, + { + "id": "file:src/services/history.py", + "type": "file", + "name": "services/history.py", + "filePath": "src/services/history.py", + "summary": "History tracking service. record_history() captures a full JSON snapshot of the current application state into application_history. list_history() returns paginated entries with computed field diffs (compute_field_diffs) comparing sequential snapshots. restore_to_version() replays a historical snapshot back onto the live row inside a transaction.", + "tags": ["service", "data-model"] + }, + { + "id": "file:src/services/interview_stage.py", + "type": "file", + "name": "services/interview_stage.py", + "filePath": "src/services/interview_stage.py", + "summary": "Interview stage service. Implements create_interview_stage, update_interview_stage (partial updates via model_fields_set), and delete_interview_stage. Each mutation bumps the parent application's updated_at and records history.", + "tags": ["service", "data-model"] + }, + { + "id": "file:src/services/csv.py", + "type": "file", + "name": "services/csv.py", + "filePath": "src/services/csv.py", + "summary": "CSV import and export service. get_sample_csv() returns a template with headers and one example row. export_to_csv() fetches all applications ordered by date_applied and serialises to CSV. import_from_csv() parses the uploaded file through CsvRow validation, deduplicates by job_posting_url, and bulk-inserts rows, recording history for each and collecting per-row errors.", + "tags": ["service"] + }, + { + "id": "file:src/services/shared.py", + "type": "file", + "name": "services/shared.py", + "filePath": "src/services/shared.py", + "summary": "Shared service utilities. parse_date() converts YYYY-MM-DD strings to datetime.date for asyncpg. format_date() and format_datetime() convert DB values to ISO strings. row_to_application_response() maps raw asyncpg Record rows (including nested interview_stages) to ApplicationResponse, sorting stages by order.", + "tags": ["service", "utility"] + }, + { + "id": "file:migrations/001_initial.sql", + "type": "file", + "name": "001_initial.sql", + "filePath": "migrations/001_initial.sql", + "summary": "Initial database migration creating the python_fastapi schema. Defines three PostgreSQL custom ENUMs (application_status, company_category, job_source), and three tables: applications (UUID PK, 20 columns with optional salary/date/notes fields), interview_stages (UUID PK, FK to applications with CASCADE DELETE), and application_history (UUID PK, FK to applications, JSONB snapshot column for full-state versioning).", + "tags": ["data-model", "schema"] + }, + { + "id": "file:migrations/run.py", + "type": "file", + "name": "migrations/run.py", + "filePath": "migrations/run.py", + "summary": "Migration runner script. Connects directly to PostgreSQL, creates the python_fastapi schema and a schema_migrations tracking table, then applies any unapplied .sql files in alphabetical order. Can be run as `python -m migrations.run`.", + "tags": ["service", "config"] + }, + { + "id": "file:tests/test_helpers.py", + "type": "file", + "name": "tests/test_helpers.py", + "filePath": "tests/test_helpers.py", + "summary": "Unit tests for pure helper functions in services/shared.py and services/history.py. Covers parse_date, format_date, format_datetime, build_description, compute_field_diffs, and row_to_application_response including stage sorting and camelCase serialisation.", + "tags": ["test"] + }, + { + "id": "file:tests/test_schemas.py", + "type": "file", + "name": "tests/test_schemas.py", + "filePath": "tests/test_schemas.py", + "summary": "Unit tests for Pydantic schema validation. Tests camelCase alias acceptance, snake_case construction, serialisation, field range constraints (skills_match 1–5, salary >= 0, name lengths), enum validation, and the critical model_fields_set behaviour for partial PATCH semantics.", + "tags": ["test"] + }, + { + "id": "file:pyproject.toml", + "type": "config", + "name": "pyproject.toml", + "filePath": "pyproject.toml", + "summary": "Project manifest managed by uv. Declares runtime dependencies (fastapi, uvicorn, asyncpg, pydantic, python-dotenv, python-multipart) and dev dependencies (ruff, mypy, pytest, pytest-asyncio). Configures ruff linting (E/F/I/N/W/UP rules) and mypy strict mode.", + "tags": ["config"] + }, + { + "id": "file:CLAUDE.md", + "type": "document", + "name": "CLAUDE.md", + "filePath": "CLAUDE.md", + "summary": "Stack-specific developer guidance for AI coding assistants. Documents port (5160), schema (python_fastapi), functional service style, CamelModel pattern, partial PATCH via model_fields_set, asyncpg date/SSL gotchas, PostgreSQL enum cast syntax, and dev startup commands.", + "tags": ["documentation"] + } + ], + "edges": [ + { + "source": "file:src/main.py", + "target": "file:src/db.py", + "type": "imports", + "label": "imports create_pool, close_pool" + }, + { + "source": "file:src/main.py", + "target": "file:src/routes/applications.py", + "type": "imports", + "label": "include_router applications_router" + }, + { + "source": "file:src/main.py", + "target": "file:src/routes/health.py", + "type": "imports", + "label": "include_router health_router" + }, + { + "source": "file:src/__main__.py", + "target": "file:src/config.py", + "type": "imports", + "label": "imports get_api_port" + }, + { + "source": "file:src/__main__.py", + "target": "file:src/main.py", + "type": "depends_on", + "label": "references src.main:app for uvicorn" + }, + { + "source": "file:src/db.py", + "target": "file:src/config.py", + "type": "imports", + "label": "imports get_database_url" + }, + { + "source": "file:src/routes/applications.py", + "target": "file:src/db.py", + "type": "imports", + "label": "imports get_pool" + }, + { + "source": "file:src/routes/applications.py", + "target": "file:src/schemas.py", + "type": "imports", + "label": "imports request/response schemas" + }, + { + "source": "file:src/routes/applications.py", + "target": "file:src/services/application.py", + "type": "depends_on", + "label": "delegates to app_service functions" + }, + { + "source": "file:src/routes/applications.py", + "target": "file:src/services/history.py", + "type": "depends_on", + "label": "delegates to history_service.list_history, restore_to_version" + }, + { + "source": "file:src/routes/applications.py", + "target": "file:src/services/interview_stage.py", + "type": "depends_on", + "label": "delegates to stage_service functions" + }, + { + "source": "file:src/routes/applications.py", + "target": "file:src/services/csv.py", + "type": "depends_on", + "label": "delegates to export_to_csv, import_from_csv, get_sample_csv" + }, + { + "source": "file:src/services/application.py", + "target": "file:src/schemas.py", + "type": "imports", + "label": "imports ApplicationResponse, CreateApplicationRequest, etc." + }, + { + "source": "file:src/services/application.py", + "target": "file:src/services/history.py", + "type": "depends_on", + "label": "calls record_history, build_description" + }, + { + "source": "file:src/services/application.py", + "target": "file:src/services/shared.py", + "type": "imports", + "label": "imports parse_date, row_to_application_response" + }, + { + "source": "file:src/services/history.py", + "target": "file:src/schemas.py", + "type": "imports", + "label": "imports FieldChange, HistoryEntryResponse, etc." + }, + { + "source": "file:src/services/history.py", + "target": "file:src/services/shared.py", + "type": "imports", + "label": "imports parse_date, row_to_application_response" + }, + { + "source": "file:src/services/interview_stage.py", + "target": "file:src/schemas.py", + "type": "imports", + "label": "imports CreateInterviewStageRequest, InterviewStageResponse, etc." + }, + { + "source": "file:src/services/interview_stage.py", + "target": "file:src/services/history.py", + "type": "depends_on", + "label": "calls record_history, build_description" + }, + { + "source": "file:src/services/interview_stage.py", + "target": "file:src/services/shared.py", + "type": "imports", + "label": "imports format_date, parse_date" + }, + { + "source": "file:src/services/csv.py", + "target": "file:src/schemas.py", + "type": "imports", + "label": "imports CsvRow, ImportError, ImportResult" + }, + { + "source": "file:src/services/csv.py", + "target": "file:src/services/history.py", + "type": "depends_on", + "label": "calls record_history, build_description" + }, + { + "source": "file:src/services/csv.py", + "target": "file:src/services/shared.py", + "type": "imports", + "label": "imports parse_date" + }, + { + "source": "file:src/services/shared.py", + "target": "file:src/schemas.py", + "type": "imports", + "label": "imports ApplicationResponse, InterviewStageResponse" + }, + { + "source": "file:src/schemas.py", + "target": "file:src/enums.py", + "type": "imports", + "label": "imports ApplicationStatus, CompanyCategory, JobSource" + }, + { + "source": "file:migrations/run.py", + "target": "file:migrations/001_initial.sql", + "type": "depends_on", + "label": "discovers and applies SQL migration files" + }, + { + "source": "file:migrations/001_initial.sql", + "target": "file:src/db.py", + "type": "related", + "label": "defines the python_fastapi schema that db.py search_path targets" + }, + { + "source": "file:migrations/001_initial.sql", + "target": "file:src/enums.py", + "type": "related", + "label": "PostgreSQL ENUMs mirror Python StrEnum definitions" + }, + { + "source": "file:tests/test_helpers.py", + "target": "file:src/services/shared.py", + "type": "imports", + "label": "tests parse_date, format_date, format_datetime, row_to_application_response" + }, + { + "source": "file:tests/test_helpers.py", + "target": "file:src/services/history.py", + "type": "imports", + "label": "tests build_description, compute_field_diffs" + }, + { + "source": "file:tests/test_schemas.py", + "target": "file:src/schemas.py", + "type": "imports", + "label": "tests all request/response schema validation" + }, + { + "source": "file:tests/test_schemas.py", + "target": "file:src/enums.py", + "type": "imports", + "label": "imports ApplicationStatus, CompanyCategory, JobSource" + }, + { + "source": "file:pyproject.toml", + "target": "file:src/main.py", + "type": "related", + "label": "declares the package and its runtime dependencies" + }, + { + "source": "file:CLAUDE.md", + "target": "file:src/main.py", + "type": "related", + "label": "documents stack patterns and gotchas for this codebase" + } + ], + "layers": [ + { + "id": "layer:config", + "name": "Config Layer", + "description": "Application bootstrap, environment configuration, and server entry points. Includes the FastAPI factory, uvicorn launcher, environment loading, and project manifest.", + "nodeIds": [ + "file:src/main.py", + "file:src/__main__.py", + "file:src/config.py", + "file:pyproject.toml" + ] + }, + { + "id": "layer:api", + "name": "API Layer", + "description": "FastAPI APIRouter modules that define HTTP endpoints, parse request parameters, call service functions, and format responses. Mounts at /health and /applications.", + "nodeIds": [ + "file:src/routes/applications.py", + "file:src/routes/health.py" + ] + }, + { + "id": "layer:types", + "name": "Types Layer", + "description": "Pydantic v2 schema definitions (CamelModel base, all request/response models, CSV schemas, error types) and Python StrEnum definitions mirroring PostgreSQL custom types.", + "nodeIds": [ + "file:src/schemas.py", + "file:src/enums.py" + ] + }, + { + "id": "layer:service", + "name": "Service Layer", + "description": "Business logic and data access functions. All service functions accept an asyncpg.Pool as first argument (functional, not class-based). Covers application CRUD, history tracking, interview stage management, and CSV import/export.", + "nodeIds": [ + "file:src/services/application.py", + "file:src/services/history.py", + "file:src/services/interview_stage.py", + "file:src/services/csv.py", + "file:src/services/shared.py" + ] + }, + { + "id": "layer:data", + "name": "Data Layer", + "description": "Database connectivity and schema migrations. asyncpg pool lifecycle management and the SQL migration that creates the python_fastapi schema, enums, and tables.", + "nodeIds": [ + "file:src/db.py", + "file:migrations/001_initial.sql", + "file:migrations/run.py" + ] + }, + { + "id": "layer:test", + "name": "Test Layer", + "description": "pytest unit tests covering pure helper functions and Pydantic schema validation logic without requiring a live database connection.", + "nodeIds": [ + "file:tests/test_helpers.py", + "file:tests/test_schemas.py" + ] + }, + { + "id": "layer:docs", + "name": "Documentation Layer", + "description": "Stack-specific developer notes and AI coding guidance.", + "nodeIds": [ + "file:CLAUDE.md" + ] + } + ], + "tour": [ + { + "order": 1, + "title": "Start Here: Application Entry Point", + "description": "src/main.py is where the FastAPI application is created. It wires together all the pieces: the asyncpg connection pool lifecycle via the lifespan context manager, CORS middleware for cross-origin frontend requests, global exception handlers that normalise 422 and 500 errors, and router inclusion for /health and /applications.", + "nodeIds": ["file:src/main.py"] + }, + { + "order": 2, + "title": "Configuration and Startup", + "description": "src/config.py loads environment variables from the fastapi/.env file only (not parent directories), providing database URL and port. src/__main__.py is the uvicorn launcher used with `python -m src`. src/db.py manages the asyncpg connection pool singleton with the python_fastapi search_path set.", + "nodeIds": ["file:src/config.py", "file:src/__main__.py", "file:src/db.py"] + }, + { + "order": 3, + "title": "Data Model: Enums and Schema", + "description": "src/enums.py defines Python StrEnums (ApplicationStatus, CompanyCategory, JobSource) that mirror the PostgreSQL custom ENUMs. src/schemas.py contains all Pydantic v2 models. The CamelModel base class auto-generates camelCase aliases — the API speaks camelCase while the Python code stays snake_case. UpdateApplicationRequest uses model_fields_set for partial PATCH semantics.", + "nodeIds": ["file:src/enums.py", "file:src/schemas.py"] + }, + { + "order": 4, + "title": "Database Schema", + "description": "migrations/001_initial.sql creates the python_fastapi schema with three PostgreSQL custom ENUMs and three tables: applications (the core entity), interview_stages (child table with CASCADE DELETE), and application_history (JSONB snapshots for versioning). migrations/run.py applies these files in order with idempotency tracking.", + "nodeIds": ["file:migrations/001_initial.sql", "file:migrations/run.py"] + }, + { + "order": 5, + "title": "API Endpoints", + "description": "src/routes/applications.py defines all 14 endpoints under /applications: list with filtering/sorting/pagination, CRUD, archive/restore, CSV import/export, interview stage CRUD, and history list/restore. src/routes/health.py provides a simple readiness check. Both routers delegate all logic to service modules.", + "nodeIds": ["file:src/routes/applications.py", "file:src/routes/health.py"] + }, + { + "order": 6, + "title": "Core Business Logic", + "description": "src/services/application.py implements the application CRUD operations using dynamic SQL (safe whitelist-based column injection for partial updates). src/services/shared.py provides date formatting helpers and the row_to_application_response mapper. Both are foundational utilities used by all other services.", + "nodeIds": ["file:src/services/application.py", "file:src/services/shared.py"] + }, + { + "order": 7, + "title": "History Tracking", + "description": "src/services/history.py implements the versioning system. Every mutation calls record_history() which serialises the full current application state as JSONB. list_history() computes field-level diffs between sequential snapshots. restore_to_version() replays a snapshot inside a database transaction, including re-inserting interview stages.", + "nodeIds": ["file:src/services/history.py"] + }, + { + "order": 8, + "title": "Interview Stages and CSV", + "description": "src/services/interview_stage.py manages the interview_stages child table with partial updates and automatic history recording. src/services/csv.py provides bulk data operations: a sample template, full export, and import with per-row validation via CsvRow schemas, URL-based deduplication, and per-row error collection.", + "nodeIds": ["file:src/services/interview_stage.py", "file:src/services/csv.py"] + }, + { + "order": 9, + "title": "Tests", + "description": "tests/test_helpers.py covers all pure helper functions (date parsing/formatting, row mapping, diff computation) without requiring a database. tests/test_schemas.py validates all Pydantic schema constraints including the critical model_fields_set behaviour needed for partial PATCH.", + "nodeIds": ["file:tests/test_helpers.py", "file:tests/test_schemas.py"] + } + ] +} diff --git a/fastapi/.understand-anything/meta.json b/fastapi/.understand-anything/meta.json new file mode 100644 index 00000000..2cb0b562 --- /dev/null +++ b/fastapi/.understand-anything/meta.json @@ -0,0 +1,26 @@ +{ + "projectName": "fastapi-application-tracker", + "description": "Application Tracker REST API built with Python FastAPI, asyncpg for PostgreSQL, and Pydantic v2 models. Runs on port 5160, uses the python_fastapi PostgreSQL schema, and is consumed by tanstack-start-ui.", + "gitHash": "e5054096efec8165e7d4cfe11fc103f77ad2d98f", + "generatedAt": "2026-05-04T00:00:00.000Z", + "pluginVersion": "2.5.1", + "languages": [ + "Python", + "SQL" + ], + "frameworks": [ + "FastAPI", + "asyncpg", + "Pydantic v2", + "uvicorn", + "pytest" + ], + "entryPoint": "src/main.py", + "stats": { + "filesAnalyzed": 24, + "totalNodes": 19, + "totalEdges": 34, + "totalLayers": 7, + "tourSteps": 9 + } +} diff --git a/go-api/.understand-anything/.understandignore b/go-api/.understand-anything/.understandignore new file mode 100644 index 00000000..d9f87033 --- /dev/null +++ b/go-api/.understand-anything/.understandignore @@ -0,0 +1,5 @@ +# .understandignore — patterns for files/dirs to exclude from analysis +# Built-in defaults (always excluded): node_modules/, .git/, dist/, build/, *.lock + +# Exclude generated sqlc code (auto-generated, not hand-authored) +# sql/db/ # uncomment if sqlc output is too noisy diff --git a/go-api/.understand-anything/knowledge-graph.json b/go-api/.understand-anything/knowledge-graph.json new file mode 100644 index 00000000..f4e28b10 --- /dev/null +++ b/go-api/.understand-anything/knowledge-graph.json @@ -0,0 +1,928 @@ +{ + "version": "1.0.0", + "project": { + "name": "go-api", + "languages": [ + "Go", + "SQL" + ], + "frameworks": [ + "Gin", + "pgx", + "sqlc", + "golang-migrate" + ], + "description": "Go REST API using Gin framework, pgx for PostgreSQL (go_gin schema), and sqlc for type-safe query generation. Runs on port 5070.", + "analyzedAt": "2026-05-05T01:26:52Z", + "gitCommitHash": "e5054096efec8165e7d4cfe11fc103f77ad2d98f" + }, + "nodes": [ + { + "id": "file:cmd/server/main.go", + "type": "file", + "name": "cmd/server/main.go", + "filePath": "cmd/server/main.go", + "summary": "Application entry point. Loads config, runs embedded SQL migrations via golang-migrate/iofs, creates a pgxpool connection pool, constructs a Gin engine with Logger and Recovery middleware, registers all routes, and starts the HTTP server on the configured port (default 5070).", + "tags": [ + "entrypoint", + "server", + "gin", + "migration", + "main" + ], + "complexity": "moderate" + }, + { + "id": "file:cmd/migrate/main.go", + "type": "file", + "name": "cmd/migrate/main.go", + "filePath": "cmd/migrate/main.go", + "summary": "Standalone CLI command for running database migrations from the filesystem (file:// source). Reads DATABASE_URL and MIGRATIONS_PATH from the environment, applies pending migrations, and reports the current version. Used outside the main server lifecycle.", + "tags": [ + "migration", + "cli", + "main", + "database" + ], + "complexity": "low" + }, + { + "id": "file:internal/config/config.go", + "type": "file", + "name": "internal/config/config.go", + "filePath": "internal/config/config.go", + "summary": "Loads application configuration from environment variables (DATABASE_URL, PORT, CORS_ORIGIN). Attempts to load a .env file, falling back to the monorepo root .env. Automatically appends search_path=go_gin to the database URL if not already present. Returns a Config struct with defaults: port 5070, CORS origin http://localhost:3060.", + "tags": [ + "config", + "environment", + "dotenv", + "postgres", + "cors" + ], + "complexity": "low" + }, + { + "id": "file:internal/db/models.go", + "type": "file", + "name": "internal/db/models.go", + "filePath": "internal/db/models.go", + "summary": "Defines Go structs for the three database entities: Application (from go_gin.applications), InterviewStage (from go_gin.interview_stages), and ApplicationSnapshot (from go_gin.application_snapshots). Uses pgtype for nullable PostgreSQL fields. Also defines helper nullable types: NullString, NullInt4, NullDate.", + "tags": [ + "model", + "database", + "struct", + "pgtype" + ], + "complexity": "low" + }, + { + "id": "file:internal/db/pool.go", + "type": "file", + "name": "internal/db/pool.go", + "filePath": "internal/db/pool.go", + "summary": "Creates and configures a pgxpool.Pool with a maximum of 10 connections. Parses the database URL and returns the pool for use throughout the application.", + "tags": [ + "database", + "pool", + "pgx", + "connection" + ], + "complexity": "low" + }, + { + "id": "file:internal/db/query.sql.go", + "type": "file", + "name": "internal/db/query.sql.go", + "filePath": "internal/db/query.sql.go", + "summary": "Hand-written data access layer (sqlc-style). Contains all SQL query functions for applications (List, Count, Get, Create, Update, Delete, Archive, Unarchive), interview stages (Get, Create, Update, Delete), and snapshots (Create, Get, GetAll, GetNext). Implements a querier interface supporting both pool and transaction usage. Dynamic sort column injection uses an allowlist to prevent SQL injection.", + "tags": [ + "database", + "query", + "sql", + "pgx", + "dao", + "transaction" + ], + "complexity": "high" + }, + { + "id": "file:internal/handler/router.go", + "type": "file", + "name": "internal/handler/router.go", + "filePath": "internal/handler/router.go", + "summary": "Registers all HTTP routes on the Gin engine. Sets up CORS middleware (Allow-Origin, Methods, Headers, OPTIONS preflight). Groups routes under /applications: CSV import/export/sample-csv (placed before /:id to avoid routing conflicts), CRUD operations, archive/restore, history, and interview stage management endpoints.", + "tags": [ + "router", + "gin", + "cors", + "middleware", + "http", + "routes" + ], + "complexity": "low" + }, + { + "id": "file:internal/handler/applications.go", + "type": "file", + "name": "internal/handler/applications.go", + "filePath": "internal/handler/applications.go", + "summary": "HTTP handlers for application CRUD and archive operations: listApplications (GET /applications with filtering, sorting, pagination), getApplication (GET /:id), createApplication (POST), updateApplication (PATCH /:id), deleteApplication (DELETE /:id), archiveApplication (POST /:id/archive), unarchiveApplication (POST /:id/restore). Maps camelCase query params to snake_case DB columns.", + "tags": [ + "handler", + "http", + "gin", + "crud", + "application", + "archive" + ], + "complexity": "moderate" + }, + { + "id": "file:internal/handler/stages.go", + "type": "file", + "name": "internal/handler/stages.go", + "filePath": "internal/handler/stages.go", + "summary": "HTTP handlers for interview stage management: addStage (POST /applications/:id/interview-stages), updateStage (PATCH /:id/interview-stages/:stageId), removeStage (DELETE /:id/interview-stages/:stageId). Delegates to service layer; returns the full ApplicationResponse after each mutation.", + "tags": [ + "handler", + "http", + "gin", + "stages", + "interview" + ], + "complexity": "low" + }, + { + "id": "file:internal/handler/history.go", + "type": "file", + "name": "internal/handler/history.go", + "filePath": "internal/handler/history.go", + "summary": "HTTP handlers for application history: listHistory (GET /applications/:id/history) returns all snapshots with field-level diffs; restoreHistory (POST /applications/:id/history/:historyId/restore) reverts the application to a prior snapshot state within a transaction.", + "tags": [ + "handler", + "http", + "gin", + "history", + "snapshot", + "restore" + ], + "complexity": "low" + }, + { + "id": "file:internal/handler/csv.go", + "type": "file", + "name": "internal/handler/csv.go", + "filePath": "internal/handler/csv.go", + "summary": "HTTP handlers for CSV operations: importCSV (POST /applications/import, multipart/form-data file upload), exportCSV (GET /applications/export, streams CSV attachment), sampleCSV (GET /applications/sample-csv, returns template CSV). Delegates parsing/generation to service layer.", + "tags": [ + "handler", + "http", + "gin", + "csv", + "import", + "export" + ], + "complexity": "low" + }, + { + "id": "file:internal/service/applications.go", + "type": "file", + "name": "internal/service/applications.go", + "filePath": "internal/service/applications.go", + "summary": "Core application business logic. Defines ApplicationInput (create/update payload), ApplicationResponse (API output), InterviewStageResponse, and ListParams. Implements ListApplications (with pagination and filter), GetApplication, CreateApplication (with validation, snapshot creation), UpdateApplication (falls back to existing values for empty fields), DeleteApplication, ArchiveApplication, and UnarchiveApplication. Includes conversion helpers between pgtype and Go types.", + "tags": [ + "service", + "business-logic", + "application", + "validation", + "snapshot", + "pagination" + ], + "complexity": "high" + }, + { + "id": "file:internal/service/stages.go", + "type": "file", + "name": "internal/service/stages.go", + "filePath": "internal/service/stages.go", + "summary": "Interview stage service logic. Defines StageInput (JSON: name/order keys). Implements AddStage (verifies application exists, creates stage, takes snapshot), UpdateStage, RemoveStage (checks RowsAffected for ErrStageNotFound). Each mutation triggers a snapshot and returns the full ApplicationResponse.", + "tags": [ + "service", + "stages", + "interview", + "snapshot" + ], + "complexity": "moderate" + }, + { + "id": "file:internal/service/history.go", + "type": "file", + "name": "internal/service/history.go", + "filePath": "internal/service/history.go", + "summary": "History and versioning service. CreateSnapshot serializes the full application+stages state to JSONB. GetHistory returns all snapshots in DESC order with field-level diffs computed via reflect.DeepEqual. RestoreToVersion runs a transaction to revert application fields and replace all interview stages, then creates a new restore snapshot.", + "tags": [ + "service", + "history", + "snapshot", + "versioning", + "diff", + "transaction" + ], + "complexity": "high" + }, + { + "id": "file:internal/service/csv.go", + "type": "file", + "name": "internal/service/csv.go", + "filePath": "internal/service/csv.go", + "summary": "CSV import/export service. ImportCSV parses CSV using encoding/csv, validates required columns, detects duplicates by jobPostingUrl or (companyName, positionTitle) pair (both intra-file and against existing DB records), creates applications, and optionally archives them. ExportCSV serializes all applications to CSV. GetTemplate returns a sample CSV with headers and one example row.", + "tags": [ + "service", + "csv", + "import", + "export", + "duplicate-detection" + ], + "complexity": "moderate" + }, + { + "id": "file:internal/migrations/embed.go", + "type": "file", + "name": "internal/migrations/embed.go", + "filePath": "internal/migrations/embed.go", + "summary": "Uses Go's embed directive to bundle all *.sql migration files from the internal/migrations directory into the compiled binary. Exposes the FS variable used by golang-migrate's iofs source driver at server startup.", + "tags": [ + "migration", + "embed", + "go-embed" + ], + "complexity": "low" + }, + { + "id": "file:internal/migrations/001_initial.up.sql", + "type": "file", + "name": "internal/migrations/001_initial.up.sql", + "filePath": "internal/migrations/001_initial.up.sql", + "summary": "Initial database schema for the go_gin schema. Creates PostgreSQL enums (application_status, company_category, job_source, skills_match, performance_rating) and three tables: applications (UUID PK, full job application tracking fields), interview_stages (FK to applications with CASCADE delete), and application_snapshots (JSONB snapshot_data for version history).", + "tags": [ + "migration", + "sql", + "schema", + "database", + "ddl" + ], + "complexity": "moderate" + }, + { + "id": "file:internal/migrations/002_drop_enums.up.sql", + "type": "file", + "name": "internal/migrations/002_drop_enums.up.sql", + "filePath": "internal/migrations/002_drop_enums.up.sql", + "summary": "Alters enum-typed columns in go_gin.applications and interview_stages to TEXT type, then drops the custom enum types. This migration switches from PostgreSQL enum constraints to application-level validation for flexibility.", + "tags": [ + "migration", + "sql", + "schema", + "enum", + "alter" + ], + "complexity": "low" + }, + { + "id": "file:internal/migrations/003_align_with_nestjs.up.sql", + "type": "file", + "name": "internal/migrations/003_align_with_nestjs.up.sql", + "filePath": "internal/migrations/003_align_with_nestjs.up.sql", + "summary": "Alignment migration adding missing columns to match the NestJS schema: salary_min, salary_max, cover_letter_required, offer_due_date, special_requirements, and company_career_url to the applications table.", + "tags": [ + "migration", + "sql", + "schema", + "alter", + "alignment" + ], + "complexity": "low" + }, + { + "id": "file:internal/migrations/004_align_job_source_with_nestjs.up.sql", + "type": "file", + "name": "internal/migrations/004_align_job_source_with_nestjs.up.sql", + "filePath": "internal/migrations/004_align_job_source_with_nestjs.up.sql", + "summary": "Updates job_source values in existing data from underscore format to hyphenated format (e.g., company_website -> company-website) to align with the NestJS API's value format.", + "tags": [ + "migration", + "sql", + "data-migration", + "job-source" + ], + "complexity": "low" + }, + { + "id": "file:internal/migrations/005_align_status_with_nestjs.up.sql", + "type": "file", + "name": "internal/migrations/005_align_status_with_nestjs.up.sql", + "filePath": "internal/migrations/005_align_status_with_nestjs.up.sql", + "summary": "Updates status values in existing data from underscore format to hyphenated format (e.g., phone_screen -> phone-screen, in_progress -> in-progress) to align with the cross-stack application status convention.", + "tags": [ + "migration", + "sql", + "data-migration", + "status" + ], + "complexity": "low" + }, + { + "id": "file:sql/queries/applications.sql", + "type": "file", + "name": "sql/queries/applications.sql", + "filePath": "sql/queries/applications.sql", + "summary": "Source SQL query definitions for application CRUD operations used by sqlc code generation. Defines named queries for listing, counting, getting, creating, updating, deleting, archiving, and unarchiving applications in the go_gin schema.", + "tags": [ + "sql", + "queries", + "sqlc", + "application" + ], + "complexity": "moderate" + }, + { + "id": "file:sql/queries/history.sql", + "type": "file", + "name": "sql/queries/history.sql", + "filePath": "sql/queries/history.sql", + "summary": "Source SQL query definitions for snapshot/history operations used by sqlc. Defines named queries for creating snapshots, listing snapshots by application, fetching a specific snapshot, and computing the next sequence number.", + "tags": [ + "sql", + "queries", + "sqlc", + "history", + "snapshot" + ], + "complexity": "low" + }, + { + "id": "file:sql/queries/stages.sql", + "type": "file", + "name": "sql/queries/stages.sql", + "filePath": "sql/queries/stages.sql", + "summary": "Source SQL query definitions for interview stage operations used by sqlc. Defines named queries for listing stages by application, creating, updating, and deleting stages.", + "tags": [ + "sql", + "queries", + "sqlc", + "stages" + ], + "complexity": "low" + }, + { + "id": "file:sqlc.yaml", + "type": "config", + "name": "sqlc.yaml", + "filePath": "sqlc.yaml", + "summary": "sqlc code generation configuration. Points to sql/queries/ for query files, migrations/ for schema, and generates Go code into internal/db using the pgx/v5 SQL package with camelCase JSON tags.", + "tags": [ + "config", + "sqlc", + "codegen" + ], + "complexity": "low" + }, + { + "id": "file:.golangci.yml", + "type": "config", + "name": ".golangci.yml", + "filePath": ".golangci.yml", + "summary": "GolangCI-Lint configuration defining linter rules and exclusions for the go-api package.", + "tags": [ + "config", + "lint", + "golangci" + ], + "complexity": "low" + }, + { + "id": "file:Dockerfile", + "type": "config", + "name": "Dockerfile", + "filePath": "Dockerfile", + "summary": "Multi-stage Docker build for the Go API. Builder stage compiles the server binary using golang:1.24-alpine. Runtime stage uses alpine:latest with ca-certificates, copies only the binary for a minimal production image.", + "tags": [ + "docker", + "build", + "deployment", + "container" + ], + "complexity": "low" + }, + { + "id": "file:go.mod", + "type": "config", + "name": "go.mod", + "filePath": "go.mod", + "summary": "Go module definition. Module path: github.com/user/application-tracker/go-api, Go 1.26.2. Direct dependencies: gin v1.10.1, golang-migrate v4.18.3, pgx/v5 v5.9.2, godotenv v1.5.1, testify v1.11.1, testcontainers-go v0.36.0.", + "tags": [ + "go", + "module", + "dependencies" + ], + "complexity": "low" + }, + { + "id": "file:CLAUDE.md", + "type": "document", + "name": "CLAUDE.md", + "filePath": "CLAUDE.md", + "summary": "Stack-specific notes for the Go Gin API. Documents port assignments (go-api: 5070, angular-ui: 3060), DB schema (go_gin), StageInput JSON key conventions (name/order not stageName/stageOrder), validation approach (service layer not binding tags), angular-ui proxy config, and server startup procedures for E2E testing.", + "tags": [ + "documentation", + "claude", + "developer-guide" + ], + "complexity": "low" + }, + { + "id": "file:tests/helper_test.go", + "type": "file", + "name": "tests/helper_test.go", + "filePath": "tests/helper_test.go", + "summary": "Test infrastructure. setupTestDB starts a PostgreSQL 18 container via testcontainers-go, creates a connection pool, and runs SQL migrations from the migrations/ directory. Handles Docker unavailability gracefully with t.Skip. runMigrations applies each .sql file in order using the test pool directly.", + "tags": [ + "test", + "integration", + "testcontainers", + "docker", + "postgres" + ], + "complexity": "moderate" + }, + { + "id": "file:tests/applications_test.go", + "type": "file", + "name": "tests/applications_test.go", + "filePath": "tests/applications_test.go", + "summary": "Integration tests for application CRUD endpoints: TestCreateApplication, list, get, update, delete, archive/restore. Uses HTTP calls against a real database via testcontainers. Defines helper functions createApp and deleteApp.", + "tags": [ + "test", + "integration", + "application", + "http", + "crud" + ], + "complexity": "moderate" + }, + { + "id": "file:tests/stages_test.go", + "type": "file", + "name": "tests/stages_test.go", + "filePath": "tests/stages_test.go", + "summary": "Integration tests for interview stage management: add, update, and remove stages via HTTP. Verifies stage appears in ApplicationResponse.interviewStages after mutations.", + "tags": [ + "test", + "integration", + "stages", + "http" + ], + "complexity": "moderate" + }, + { + "id": "file:tests/history_test.go", + "type": "file", + "name": "tests/history_test.go", + "filePath": "tests/history_test.go", + "summary": "Integration tests for application history and version restore: verifies snapshots are created on CRUD operations, history entries contain correct diffs, and restore reverts application state.", + "tags": [ + "test", + "integration", + "history", + "snapshot", + "restore" + ], + "complexity": "moderate" + }, + { + "id": "file:tests/csv_test.go", + "type": "file", + "name": "tests/csv_test.go", + "filePath": "tests/csv_test.go", + "summary": "Integration tests for CSV import and export: uploads a CSV file via multipart form, verifies rows are imported, checks duplicate detection, and validates the export endpoint returns valid CSV data.", + "tags": [ + "test", + "integration", + "csv", + "import", + "export" + ], + "complexity": "moderate" + }, + { + "id": "file:tests/server_test.go", + "type": "file", + "name": "tests/server_test.go", + "filePath": "tests/server_test.go", + "summary": "Integration test bootstrap for the HTTP server. Starts the actual Gin server in a test goroutine using a test database, providing a baseURL for all other test files in the tests package.", + "tags": [ + "test", + "server", + "gin", + "http", + "integration" + ], + "complexity": "low" + } + ], + "edges": [ + { + "source": "file:cmd/server/main.go", + "target": "file:internal/config/config.go", + "type": "imports", + "label": "config.Load()" + }, + { + "source": "file:cmd/server/main.go", + "target": "file:internal/db/pool.go", + "type": "imports", + "label": "db.New()" + }, + { + "source": "file:cmd/server/main.go", + "target": "file:internal/handler/router.go", + "type": "imports", + "label": "handler.RegisterRoutes()" + }, + { + "source": "file:cmd/server/main.go", + "target": "file:internal/migrations/embed.go", + "type": "imports", + "label": "embeddedMigrations.FS" + }, + { + "source": "file:internal/db/query.sql.go", + "target": "file:internal/db/models.go", + "type": "imports", + "label": "Application, InterviewStage, ApplicationSnapshot types" + }, + { + "source": "file:internal/handler/router.go", + "target": "file:internal/handler/applications.go", + "type": "imports", + "label": "listApplications, getApplication, createApplication, updateApplication, deleteApplication, archiveApplication, unarchiveApplication" + }, + { + "source": "file:internal/handler/router.go", + "target": "file:internal/handler/stages.go", + "type": "imports", + "label": "addStage, updateStage, removeStage" + }, + { + "source": "file:internal/handler/router.go", + "target": "file:internal/handler/history.go", + "type": "imports", + "label": "listHistory, restoreHistory" + }, + { + "source": "file:internal/handler/router.go", + "target": "file:internal/handler/csv.go", + "type": "imports", + "label": "importCSV, exportCSV, sampleCSV" + }, + { + "source": "file:internal/handler/applications.go", + "target": "file:internal/service/applications.go", + "type": "imports", + "label": "service.ListApplications, GetApplication, CreateApplication, UpdateApplication, DeleteApplication, ArchiveApplication, UnarchiveApplication" + }, + { + "source": "file:internal/handler/stages.go", + "target": "file:internal/service/stages.go", + "type": "imports", + "label": "service.AddStage, UpdateStage, RemoveStage" + }, + { + "source": "file:internal/handler/history.go", + "target": "file:internal/service/history.go", + "type": "imports", + "label": "service.GetHistory, RestoreToVersion" + }, + { + "source": "file:internal/handler/csv.go", + "target": "file:internal/service/csv.go", + "type": "imports", + "label": "service.ImportCSV, ExportCSV, GetTemplate" + }, + { + "source": "file:internal/service/applications.go", + "target": "file:internal/db/query.sql.go", + "type": "imports", + "label": "db.ListApplications, GetApplication, CreateApplication, UpdateApplication, DeleteApplication, ArchiveApplication, UnarchiveApplication, GetStagesByApplicationID" + }, + { + "source": "file:internal/service/stages.go", + "target": "file:internal/db/query.sql.go", + "type": "imports", + "label": "db.GetApplication, CreateStage, UpdateStage, DeleteStage" + }, + { + "source": "file:internal/service/history.go", + "target": "file:internal/db/query.sql.go", + "type": "imports", + "label": "db.GetSnapshotsByApplicationID, CreateSnapshot, GetSnapshot, GetNextSequenceNumber, UpdateApplication, GetStagesByApplicationID, DeleteStage, CreateStage" + }, + { + "source": "file:internal/service/csv.go", + "target": "file:internal/db/query.sql.go", + "type": "imports", + "label": "db.GetAllApplications" + }, + { + "source": "file:internal/service/csv.go", + "target": "file:internal/service/applications.go", + "type": "imports", + "label": "CreateApplication, ArchiveApplication, ApplicationInput, applyStatusDateConstraint" + }, + { + "source": "file:internal/service/stages.go", + "target": "file:internal/service/applications.go", + "type": "imports", + "label": "getApplicationWithStages, toNullableText, parseUUID, ApplicationResponse" + }, + { + "source": "file:internal/service/history.go", + "target": "file:internal/service/applications.go", + "type": "imports", + "label": "getApplicationWithStages, ApplicationResponse, InterviewStageResponse, toNullableDate, toNullableText, toNullableInt4, uuidToString, parseUUID" + }, + { + "source": "file:internal/migrations/embed.go", + "target": "file:internal/migrations/001_initial.up.sql", + "type": "embeds", + "label": "go:embed *.sql" + }, + { + "source": "file:internal/migrations/embed.go", + "target": "file:internal/migrations/002_drop_enums.up.sql", + "type": "embeds", + "label": "go:embed *.sql" + }, + { + "source": "file:internal/migrations/embed.go", + "target": "file:internal/migrations/003_align_with_nestjs.up.sql", + "type": "embeds", + "label": "go:embed *.sql" + }, + { + "source": "file:internal/migrations/embed.go", + "target": "file:internal/migrations/004_align_job_source_with_nestjs.up.sql", + "type": "embeds", + "label": "go:embed *.sql" + }, + { + "source": "file:internal/migrations/embed.go", + "target": "file:internal/migrations/005_align_status_with_nestjs.up.sql", + "type": "embeds", + "label": "go:embed *.sql" + }, + { + "source": "file:sqlc.yaml", + "target": "file:sql/queries/applications.sql", + "type": "references", + "label": "sqlc input" + }, + { + "source": "file:sqlc.yaml", + "target": "file:sql/queries/history.sql", + "type": "references", + "label": "sqlc input" + }, + { + "source": "file:sqlc.yaml", + "target": "file:sql/queries/stages.sql", + "type": "references", + "label": "sqlc input" + }, + { + "source": "file:sqlc.yaml", + "target": "file:internal/db/query.sql.go", + "type": "generates", + "label": "sqlc output" + }, + { + "source": "file:tests/helper_test.go", + "target": "file:internal/db/pool.go", + "type": "imports", + "label": "db.New()" + }, + { + "source": "file:tests/server_test.go", + "target": "file:internal/handler/router.go", + "type": "imports", + "label": "handler.RegisterRoutes()" + }, + { + "source": "file:tests/applications_test.go", + "target": "file:tests/helper_test.go", + "type": "imports", + "label": "setupTestDB, createApp, deleteApp helpers" + }, + { + "source": "file:tests/stages_test.go", + "target": "file:tests/helper_test.go", + "type": "imports", + "label": "setupTestDB helpers" + }, + { + "source": "file:tests/history_test.go", + "target": "file:tests/helper_test.go", + "type": "imports", + "label": "setupTestDB helpers" + }, + { + "source": "file:tests/csv_test.go", + "target": "file:tests/helper_test.go", + "type": "imports", + "label": "setupTestDB helpers" + } + ], + "layers": [ + { + "id": "layer:entrypoint", + "name": "Entrypoint", + "description": "Application bootstrap \u2014 wires together config, database, migrations, and HTTP engine, then starts the server. Also includes the standalone migrate CLI.", + "nodeIds": [ + "file:cmd/server/main.go", + "file:cmd/migrate/main.go" + ] + }, + { + "id": "layer:config", + "name": "Configuration", + "description": "Environment loading, defaults, and project metadata. Includes go.mod for dependency declarations and Dockerfile for container deployment.", + "nodeIds": [ + "file:internal/config/config.go", + "file:go.mod", + "file:Dockerfile", + "file:sqlc.yaml", + "file:.golangci.yml" + ] + }, + { + "id": "layer:api", + "name": "API Layer", + "description": "HTTP routing and handler functions. Translates HTTP requests into service calls and formats service results as JSON responses. Handles CORS, parameter parsing, error status codes.", + "nodeIds": [ + "file:internal/handler/router.go", + "file:internal/handler/applications.go", + "file:internal/handler/stages.go", + "file:internal/handler/history.go", + "file:internal/handler/csv.go" + ] + }, + { + "id": "layer:service", + "name": "Service Layer", + "description": "Business logic: validation, type conversion, snapshot lifecycle, pagination, duplicate detection. Composes database operations and enforces domain rules.", + "nodeIds": [ + "file:internal/service/applications.go", + "file:internal/service/stages.go", + "file:internal/service/history.go", + "file:internal/service/csv.go" + ] + }, + { + "id": "layer:data", + "name": "Data Layer", + "description": "Database models, connection pool, and SQL query functions. Implements the querier interface for transaction support. Includes all SQL source files and the embedded migration filesystem.", + "nodeIds": [ + "file:internal/db/models.go", + "file:internal/db/pool.go", + "file:internal/db/query.sql.go", + "file:internal/migrations/embed.go", + "file:internal/migrations/001_initial.up.sql", + "file:internal/migrations/002_drop_enums.up.sql", + "file:internal/migrations/003_align_with_nestjs.up.sql", + "file:internal/migrations/004_align_job_source_with_nestjs.up.sql", + "file:internal/migrations/005_align_status_with_nestjs.up.sql", + "file:sql/queries/applications.sql", + "file:sql/queries/history.sql", + "file:sql/queries/stages.sql" + ] + }, + { + "id": "layer:test", + "name": "Test Layer", + "description": "Integration tests using testcontainers-go for real PostgreSQL. Tests cover full HTTP API lifecycle: CRUD, stages, history/restore, and CSV import/export.", + "nodeIds": [ + "file:tests/helper_test.go", + "file:tests/applications_test.go", + "file:tests/stages_test.go", + "file:tests/history_test.go", + "file:tests/csv_test.go", + "file:tests/server_test.go" + ] + }, + { + "id": "layer:docs", + "name": "Documentation", + "description": "Developer-facing documentation for the stack.", + "nodeIds": [ + "file:CLAUDE.md" + ] + } + ], + "tour": [ + { + "id": "tour:entrypoint", + "title": "Start Here: Server Bootstrap", + "description": "The Go API starts in cmd/server/main.go. It loads config, runs embedded SQL migrations automatically at startup using golang-migrate with an iofs source, creates a pgxpool connection pool (max 10 conns), sets up a Gin engine with Logger + Recovery middleware, and calls handler.RegisterRoutes() before listening on port 5070.", + "order": 1, + "nodeIds": [ + "file:cmd/server/main.go", + "file:internal/config/config.go" + ] + }, + { + "id": "tour:routing", + "title": "Route Registration and CORS", + "description": "internal/handler/router.go registers every endpoint under a /applications group. It also sets up inline CORS middleware to allow the Angular UI (port 3060) to call the API. Note: CSV routes (import, export, sample-csv) are registered before /:id to prevent Gin from treating 'import' as an application UUID.", + "order": 2, + "nodeIds": [ + "file:internal/handler/router.go" + ] + }, + { + "id": "tour:handlers", + "title": "HTTP Handlers", + "description": "Four handler files map HTTP verbs to service calls. applications.go handles full CRUD plus archive/restore. stages.go handles interview stage mutations. history.go handles snapshot listing and version restore. csv.go handles multipart file upload for import and streaming CSV for export. Each handler parses request context, calls the service layer, and returns appropriate status codes.", + "order": 3, + "nodeIds": [ + "file:internal/handler/applications.go", + "file:internal/handler/stages.go", + "file:internal/handler/history.go", + "file:internal/handler/csv.go" + ] + }, + { + "id": "tour:service", + "title": "Business Logic (Service Layer)", + "description": "The service layer enforces domain rules before touching the database. applications.go defines ApplicationInput, ApplicationResponse, and helper type converters (pgtype <-> Go). It applies the status/dateApplied constraint (unsubmitted clears the date). stages.go uses name/order JSON keys (not stageName/stageOrder). history.go creates JSONB snapshots on every mutation and computes field-level diffs via reflect.DeepEqual for history entries. csv.go handles duplicate detection by jobPostingUrl or (companyName, positionTitle) pair.", + "order": 4, + "nodeIds": [ + "file:internal/service/applications.go", + "file:internal/service/stages.go", + "file:internal/service/history.go", + "file:internal/service/csv.go" + ] + }, + { + "id": "tour:data", + "title": "Data Access Layer", + "description": "The db package contains hand-written sqlc-style query functions. models.go defines the three entities (Application, InterviewStage, ApplicationSnapshot) with pgtype fields. query.sql.go implements all queries using a querier interface (satisfied by both *pgxpool.Pool and pgx.Tx), enabling history restore to run inside a transaction. Dynamic sort columns use an allowlist map to prevent SQL injection. pool.go creates the connection pool.", + "order": 5, + "nodeIds": [ + "file:internal/db/models.go", + "file:internal/db/query.sql.go", + "file:internal/db/pool.go" + ] + }, + { + "id": "tour:migrations", + "title": "Database Migrations", + "description": "Migrations live in internal/migrations/ and are embedded into the binary via Go's embed directive (embed.go). The server runs them automatically on startup via golang-migrate. The schema evolution: 001 creates tables with PostgreSQL enums; 002 drops enums in favor of TEXT + application-level validation; 003-005 add columns and standardize values to align with the NestJS stack. A separate cmd/migrate tool can run migrations from the filesystem for CI/CD pipelines.", + "order": 6, + "nodeIds": [ + "file:internal/migrations/embed.go", + "file:internal/migrations/001_initial.up.sql", + "file:internal/migrations/002_drop_enums.up.sql", + "file:internal/migrations/003_align_with_nestjs.up.sql", + "file:internal/migrations/004_align_job_source_with_nestjs.up.sql", + "file:internal/migrations/005_align_status_with_nestjs.up.sql" + ] + }, + { + "id": "tour:sqlc", + "title": "sqlc Code Generation", + "description": "sql/queries/ contains the canonical SQL query definitions (applications.sql, history.sql, stages.sql). sqlc.yaml configures code generation targeting internal/db with pgx/v5 and camelCase JSON tags. In practice the generated code was hand-written since sqlc was not available in the environment \u2014 the sql/queries/*.sql files serve as the source of truth for query intent.", + "order": 7, + "nodeIds": [ + "file:sqlc.yaml", + "file:sql/queries/applications.sql", + "file:sql/queries/history.sql", + "file:sql/queries/stages.sql", + "file:internal/db/query.sql.go" + ] + }, + { + "id": "tour:tests", + "title": "Integration Tests", + "description": "All tests are real integration tests using testcontainers-go to spin up a PostgreSQL 18 container. helper_test.go sets up the DB and runs migrations. server_test.go starts the actual Gin HTTP server. The other test files make real HTTP calls to test the full stack end-to-end. Tests skip gracefully when Docker is unavailable.", + "order": 8, + "nodeIds": [ + "file:tests/helper_test.go", + "file:tests/server_test.go", + "file:tests/applications_test.go", + "file:tests/stages_test.go", + "file:tests/history_test.go", + "file:tests/csv_test.go" + ] + } + ] +} \ No newline at end of file diff --git a/go-api/.understand-anything/meta.json b/go-api/.understand-anything/meta.json new file mode 100644 index 00000000..ab0a6f75 --- /dev/null +++ b/go-api/.understand-anything/meta.json @@ -0,0 +1,12 @@ +{ + "version": "1.0.0", + "generatedAt": "2026-05-05T01:27:02Z", + "gitCommitHash": "e5054096efec8165e7d4cfe11fc103f77ad2d98f", + "pluginVersion": "2.5.1", + "stats": { + "totalNodes": 35, + "totalEdges": 35, + "totalLayers": 7, + "tourSteps": 8 + } +} diff --git a/hono-api/.understand-anything/.understandignore b/hono-api/.understand-anything/.understandignore new file mode 100644 index 00000000..c7034f16 --- /dev/null +++ b/hono-api/.understand-anything/.understandignore @@ -0,0 +1,2 @@ +# .understandignore — patterns for files/dirs to exclude from analysis +# Built-in defaults (always excluded): node_modules/, .git/, dist/, build/, *.lock diff --git a/hono-api/.understand-anything/knowledge-graph.json b/hono-api/.understand-anything/knowledge-graph.json new file mode 100644 index 00000000..bd5e9712 --- /dev/null +++ b/hono-api/.understand-anything/knowledge-graph.json @@ -0,0 +1,678 @@ +{ + "nodes": [ + { + "id": "file:src/index.ts", + "name": "index.ts", + "filePath": "src/index.ts", + "type": "file", + "summary": "Application entry point. Creates a Hono app instance, registers CORS and logger middleware, sets up global error handling for Zod validation errors and 500s, mounts the health and applications route groups, and starts the Node.js HTTP server on the configured API_PORT (default 5030).", + "tags": [ + "entry-point", + "hono", + "middleware", + "server", + "cors", + "error-handling" + ], + "exports": [ + "app (default)" + ], + "imports": [ + "dotenv", + "@hono/node-server", + "hono", + "hono/cors", + "hono/logger", + "zod", + "./routes/health.js", + "./routes/applications.js" + ], + "complexity": "moderate" + }, + { + "id": "file:src/db/client.ts", + "name": "client.ts", + "filePath": "src/db/client.ts", + "type": "file", + "summary": "Database client setup. Creates a postgres.js connection using the DATABASE_URL environment variable, then wraps it with drizzle-orm to produce the typed `db` client used throughout the application. Exports the `db` instance and the `Database` type.", + "tags": [ + "database", + "drizzle", + "postgres", + "connection" + ], + "exports": [ + "db", + "Database" + ], + "imports": [ + "drizzle-orm/postgres-js", + "postgres", + "./schema.js" + ], + "complexity": "moderate" + }, + { + "id": "schema:src/db/schema.ts", + "name": "schema.ts", + "filePath": "src/db/schema.ts", + "type": "schema", + "summary": "Drizzle ORM schema definition for the svelte_hono PostgreSQL schema. Defines three tables: applications (main job application record with status, salary, dates, flags), interview_stages (ordered pipeline steps for each application with completion tracking), and application_history (snapshot-based audit log). Also defines three PostgreSQL enums: application_status, company_category, and job_source. Exports all table types via Drizzle's $inferSelect and $inferInsert.", + "tags": [ + "schema", + "drizzle", + "database", + "tables", + "enums", + "postgres" + ], + "exports": [ + "svelteHonoSchema", + "applicationStatusEnum", + "companyCategoryEnum", + "jobSourceEnum", + "applications", + "interviewStages", + "applicationHistory", + "applicationsRelations", + "interviewStagesRelations", + "applicationHistoryRelations", + "Application", + "NewApplication", + "InterviewStage", + "NewInterviewStage", + "ApplicationHistoryEntry", + "NewApplicationHistoryEntry", + "ApplicationStatus", + "CompanyCategory", + "JobSource" + ], + "imports": [ + "drizzle-orm/pg-core", + "drizzle-orm" + ], + "complexity": "moderate" + }, + { + "id": "endpoint:src/routes/applications.ts", + "name": "applications.ts", + "filePath": "src/routes/applications.ts", + "type": "endpoint", + "summary": "Hono router for all /applications endpoints. Implements CRUD for job applications (GET list with filtering/pagination, GET by id, POST create, PATCH update, DELETE), plus archive/restore actions (POST /:id/archive, POST /:id/restore), history browsing (GET /:id/history), history point-in-time restore (POST /:id/history/restore), and interview stage management (POST/PATCH/DELETE /:id/interview-stages/:stageId). All inputs are validated via @hono/zod-validator.", + "tags": [ + "route", + "hono", + "rest", + "crud", + "applications", + "interview-stages", + "history", + "zod-validator" + ], + "exports": [ + "applications (default)" + ], + "imports": [ + "hono", + "@hono/zod-validator", + "zod", + "../types/api.js", + "../services/application.service.js", + "../services/interview-stage.service.js", + "../services/history.service.js" + ], + "complexity": "moderate" + }, + { + "id": "endpoint:src/routes/health.ts", + "name": "health.ts", + "filePath": "src/routes/health.ts", + "type": "endpoint", + "summary": "Simple health check endpoint. Returns `{ status: 'ok', timestamp }` on GET /. Used by load balancers and monitoring tools to verify the API is running.", + "tags": [ + "route", + "health-check", + "hono" + ], + "exports": [ + "health (default)" + ], + "imports": [ + "hono" + ], + "complexity": "moderate" + }, + { + "id": "service:src/services/application.service.ts", + "name": "application.service.ts", + "filePath": "src/services/application.service.ts", + "type": "service", + "summary": "Business logic for job application CRUD. Implements listApplications (filtering by status, category, source, skillsMatch, archive state; pagination; multi-column sort with NULLS LAST), getApplication, createApplication (auto-sets dateApplied for non-unsubmitted status), updateApplication (partial updates; forces dateApplied=null when status=unsubmitted), deleteApplication, archiveApplication, and restoreApplication. Records history snapshots on every mutation via history.service.", + "tags": [ + "service", + "application", + "crud", + "drizzle", + "pagination", + "filtering" + ], + "exports": [ + "listApplications", + "getApplication", + "createApplication", + "updateApplication", + "deleteApplication", + "archiveApplication", + "restoreApplication" + ], + "imports": [ + "drizzle-orm", + "../db/client.js", + "../db/schema.js", + "../types/api.js", + "./shared.js", + "./history.service.js" + ], + "complexity": "moderate" + }, + { + "id": "service:src/services/history.service.ts", + "name": "history.service.ts", + "filePath": "src/services/history.service.ts", + "type": "service", + "summary": "Snapshot-based history and version restore service. captureSnapshot fetches the current application state. recordHistory saves a full snapshot with a monotonically incrementing sequence number and human-readable description. listHistory returns paginated entries with computed field diffs (computeFieldDiffs compares consecutive snapshots). restoreToVersion overwrites the application table and replaces interview stages from a historical snapshot, then appends a restore_version history entry. buildDescription generates action-specific strings for history entries.", + "tags": [ + "service", + "history", + "snapshot", + "versioning", + "audit-log", + "drizzle" + ], + "exports": [ + "captureSnapshot", + "getNextSequence", + "recordHistory", + "listHistory", + "restoreToVersion", + "computeFieldDiffs", + "buildDescription" + ], + "imports": [ + "drizzle-orm", + "../db/client.js", + "../db/schema.js", + "./shared.js", + "../types/api.js" + ], + "complexity": "moderate" + }, + { + "id": "service:src/services/interview-stage.service.ts", + "name": "interview-stage.service.ts", + "filePath": "src/services/interview-stage.service.ts", + "type": "service", + "summary": "CRUD service for interview stages. createInterviewStage verifies the parent application exists before inserting, then bumps the application's updatedAt and records a history event. updateInterviewStage applies partial updates (name, order, isCompleted, completedDate, notes, performanceRating) and similarly records history. deleteInterviewStage records the deletion before removing the row. All mutations use AND(stageId, applicationId) predicates for ownership verification.", + "tags": [ + "service", + "interview-stages", + "crud", + "drizzle", + "history" + ], + "exports": [ + "createInterviewStage", + "updateInterviewStage", + "deleteInterviewStage", + "getStagesByApplicationId" + ], + "imports": [ + "drizzle-orm", + "../db/client.js", + "../db/schema.js", + "../types/api.js", + "./shared.js", + "./history.service.js" + ], + "complexity": "moderate" + }, + { + "id": "file:src/services/shared.ts", + "name": "shared.ts", + "filePath": "src/services/shared.ts", + "type": "file", + "summary": "Shared data transformation utilities. formatDate normalizes dates to YYYY-MM-DD strings (handles both string and Date inputs). formatDateTime produces ISO-8601 datetime strings. toApplicationResponse is the canonical DB-to-API-response mapper for applications, sorting interview stages by order and transforming all fields.", + "tags": [ + "utility", + "data-transform", + "date-formatting", + "shared" + ], + "exports": [ + "formatDate", + "formatDateTime", + "toApplicationResponse" + ], + "imports": [ + "../db/schema.js", + "../types/api.js" + ], + "complexity": "moderate" + }, + { + "id": "file:src/types/api.ts", + "name": "api.ts", + "filePath": "src/types/api.ts", + "type": "file", + "summary": "Zod schemas and derived TypeScript types for the entire API surface. Defines: ApplicationStatusSchema, CompanyCategorySchema, JobSourceSchema enums; InterviewStageSchema, CreateInterviewStageSchema, UpdateInterviewStageSchema; ApplicationSchema, CreateApplicationSchema, UpdateApplicationSchema; ListApplicationsQuerySchema with coercion; PaginatedApplicationsSchema; HistoryEntrySchema with FieldChangeSchema; PaginatedHistorySchema; RestoreRequestSchema; ErrorResponseSchema. All types are inferred from Zod schemas, ensuring validation and types stay in sync.", + "tags": [ + "types", + "zod", + "validation", + "schemas", + "api-contract" + ], + "exports": [ + "ApplicationStatusSchema", + "ApplicationStatus", + "CompanyCategorySchema", + "CompanyCategory", + "JobSourceSchema", + "JobSource", + "InterviewStageSchema", + "InterviewStageResponse", + "CreateInterviewStageSchema", + "CreateInterviewStageInput", + "UpdateInterviewStageSchema", + "UpdateInterviewStageInput", + "ApplicationSchema", + "ApplicationResponse", + "CreateApplicationSchema", + "CreateApplicationInput", + "UpdateApplicationSchema", + "UpdateApplicationInput", + "ListApplicationsQuerySchema", + "ListApplicationsQuery", + "PaginatedApplicationsSchema", + "PaginatedApplicationsResponse", + "FieldChangeSchema", + "FieldChange", + "HistoryEntrySchema", + "HistoryEntryResponse", + "PaginatedHistorySchema", + "PaginatedHistoryResponse", + "RestoreRequestSchema", + "ErrorResponseSchema", + "ErrorResponse" + ], + "imports": [ + "zod" + ], + "complexity": "moderate" + }, + { + "id": "config:drizzle.config.ts", + "name": "drizzle.config.ts", + "filePath": "drizzle.config.ts", + "type": "config", + "summary": "Drizzle Kit configuration for database migrations. Points at src/db/schema.ts as the schema source, outputs migrations to src/db/migrations, uses the postgresql dialect, and filters to the svelte_hono schema to avoid cross-schema interference.", + "tags": [ + "config", + "drizzle-kit", + "migrations", + "database" + ], + "exports": [], + "imports": [ + "drizzle-kit" + ], + "complexity": "moderate" + }, + { + "id": "config:tsconfig.json", + "name": "tsconfig.json", + "filePath": "tsconfig.json", + "type": "config", + "summary": "TypeScript compiler configuration. Targets ES2022, uses ESNext module format with bundler resolution, enables strict mode, outputs to ./dist, and includes source maps and declaration files.", + "tags": [ + "config", + "typescript", + "build" + ], + "exports": [], + "imports": [], + "complexity": "moderate" + }, + { + "id": "config:package.json", + "name": "package.json", + "filePath": "package.json", + "type": "config", + "summary": "npm package manifest for job-tracker-hono-api. Declares ESM module type, scripts (dev via tsx watch, build via tsc, lint via eslint, db commands via drizzle-kit), and dependencies: Hono 4.x, Drizzle ORM, Zod 4.x, postgres.js, dotenv. Dev dependencies include tsx, TypeScript, ESLint, drizzle-kit.", + "tags": [ + "config", + "npm", + "dependencies", + "scripts" + ], + "exports": [], + "imports": [], + "complexity": "moderate" + }, + { + "id": "file:src/db/migrations/0000_optimal_sunfire.sql", + "name": "0000_optimal_sunfire.sql", + "filePath": "src/db/migrations/0000_optimal_sunfire.sql", + "type": "file", + "summary": "Initial Drizzle-generated SQL migration for the svelte_hono schema. Creates the application_status, company_category, and job_source enums, the applications table, interview_stages table with a foreign key cascade to applications, and the application_history table with a foreign key cascade to applications.", + "tags": [ + "migration", + "sql", + "database", + "schema" + ], + "exports": [], + "imports": [], + "complexity": "moderate" + } + ], + "edges": [ + { + "source": "file:src/index.ts", + "target": "endpoint:src/routes/health.ts", + "type": "imports", + "label": "imports health route" + }, + { + "source": "file:src/index.ts", + "target": "endpoint:src/routes/applications.ts", + "type": "imports", + "label": "imports applications route" + }, + { + "source": "endpoint:src/routes/applications.ts", + "target": "file:src/types/api.ts", + "type": "imports", + "label": "imports Zod schemas and types" + }, + { + "source": "endpoint:src/routes/applications.ts", + "target": "service:src/services/application.service.ts", + "type": "imports", + "label": "calls application service" + }, + { + "source": "endpoint:src/routes/applications.ts", + "target": "service:src/services/interview-stage.service.ts", + "type": "imports", + "label": "calls interview stage service" + }, + { + "source": "endpoint:src/routes/applications.ts", + "target": "service:src/services/history.service.ts", + "type": "imports", + "label": "calls history service" + }, + { + "source": "service:src/services/application.service.ts", + "target": "file:src/db/client.ts", + "type": "imports", + "label": "uses db client" + }, + { + "source": "service:src/services/application.service.ts", + "target": "schema:src/db/schema.ts", + "type": "imports", + "label": "uses schema tables" + }, + { + "source": "service:src/services/application.service.ts", + "target": "file:src/types/api.ts", + "type": "imports", + "label": "imports API types" + }, + { + "source": "service:src/services/application.service.ts", + "target": "file:src/services/shared.ts", + "type": "imports", + "label": "uses toApplicationResponse" + }, + { + "source": "service:src/services/application.service.ts", + "target": "service:src/services/history.service.ts", + "type": "imports", + "label": "calls recordHistory/buildDescription" + }, + { + "source": "service:src/services/history.service.ts", + "target": "file:src/db/client.ts", + "type": "imports", + "label": "uses db client" + }, + { + "source": "service:src/services/history.service.ts", + "target": "schema:src/db/schema.ts", + "type": "imports", + "label": "uses schema tables" + }, + { + "source": "service:src/services/history.service.ts", + "target": "file:src/services/shared.ts", + "type": "imports", + "label": "uses toApplicationResponse" + }, + { + "source": "service:src/services/history.service.ts", + "target": "file:src/types/api.ts", + "type": "imports", + "label": "imports API types" + }, + { + "source": "service:src/services/interview-stage.service.ts", + "target": "file:src/db/client.ts", + "type": "imports", + "label": "uses db client" + }, + { + "source": "service:src/services/interview-stage.service.ts", + "target": "schema:src/db/schema.ts", + "type": "imports", + "label": "uses schema tables" + }, + { + "source": "service:src/services/interview-stage.service.ts", + "target": "file:src/types/api.ts", + "type": "imports", + "label": "imports API types" + }, + { + "source": "service:src/services/interview-stage.service.ts", + "target": "file:src/services/shared.ts", + "type": "imports", + "label": "uses formatDate" + }, + { + "source": "service:src/services/interview-stage.service.ts", + "target": "service:src/services/history.service.ts", + "type": "imports", + "label": "calls recordHistory/buildDescription" + }, + { + "source": "file:src/services/shared.ts", + "target": "schema:src/db/schema.ts", + "type": "imports", + "label": "imports DB types" + }, + { + "source": "file:src/services/shared.ts", + "target": "file:src/types/api.ts", + "type": "imports", + "label": "imports API types" + }, + { + "source": "file:src/db/client.ts", + "target": "schema:src/db/schema.ts", + "type": "imports", + "label": "imports full schema for Drizzle" + }, + { + "source": "config:drizzle.config.ts", + "target": "schema:src/db/schema.ts", + "type": "references", + "label": "references schema for migration generation" + } + ], + "layers": [ + { + "id": "layer:config", + "name": "Configuration", + "description": "Project configuration files including TypeScript compiler settings, npm package manifest, Drizzle Kit migration configuration, and ESLint rules.", + "nodeIds": [ + "config:package.json", + "config:tsconfig.json", + "config:drizzle.config.ts" + ] + }, + { + "id": "layer:database", + "name": "Database", + "description": "Data layer: Drizzle ORM schema definitions (tables, enums, relations for the svelte_hono PostgreSQL schema), the typed database client singleton, and the initial SQL migration.", + "nodeIds": [ + "schema:src/db/schema.ts", + "file:src/db/client.ts", + "file:src/db/migrations/0000_optimal_sunfire.sql" + ] + }, + { + "id": "layer:types", + "name": "API Contract", + "description": "Zod schemas and inferred TypeScript types that define the API surface: request/response shapes, enum values, pagination structures, and error formats.", + "nodeIds": [ + "file:src/types/api.ts" + ] + }, + { + "id": "layer:services", + "name": "Service Layer", + "description": "Business logic services that perform database queries, enforce domain rules (archive/restore, dateApplied auto-management, history recording), and transform DB rows to API responses.", + "nodeIds": [ + "service:src/services/application.service.ts", + "service:src/services/history.service.ts", + "service:src/services/interview-stage.service.ts", + "file:src/services/shared.ts" + ] + }, + { + "id": "layer:routes", + "name": "HTTP Layer", + "description": "Hono route handlers that validate incoming requests with Zod, dispatch to service functions, and format HTTP responses. Includes the applications router and the health check endpoint.", + "nodeIds": [ + "endpoint:src/routes/applications.ts", + "endpoint:src/routes/health.ts" + ] + }, + { + "id": "layer:entry", + "name": "Entry Point", + "description": "Application bootstrap: creates the Hono app, registers middleware (CORS, logger, global error handler), mounts route groups, and starts the Node.js HTTP server.", + "nodeIds": [ + "file:src/index.ts" + ] + } + ], + "tour": [ + { + "order": 1, + "title": "Start Here: Application Entry Point", + "description": "src/index.ts is where the Hono app is assembled and the Node.js server is started. It wires together CORS and logging middleware, registers the /health and /applications route groups, installs a global error handler that turns Zod validation errors into structured 400 responses, and launches on API_PORT (default 5030). Reading this file gives you the full request lifecycle at a glance.", + "nodeIds": [ + "file:src/index.ts" + ], + "layerId": "layer:entry" + }, + { + "order": 2, + "title": "Database Schema", + "description": "src/db/schema.ts defines the entire data model for the svelte_hono PostgreSQL schema using Drizzle ORM. Three tables: applications (the core record), interview_stages (ordered process steps with completion tracking), and application_history (snapshot-based audit log). Three enums: application_status, company_category, and job_source. All exported TypeScript types are Drizzle-inferred, so schema and types are always in sync.", + "nodeIds": [ + "schema:src/db/schema.ts" + ], + "layerId": "layer:database" + }, + { + "order": 3, + "title": "API Contract (Zod Schemas)", + "description": "src/types/api.ts is the single source of truth for every request and response shape. All Zod schemas (CreateApplicationSchema, UpdateApplicationSchema, ListApplicationsQuerySchema, etc.) are defined here and their TypeScript types are inferred directly from them. This means changing a schema immediately flows to validation and type-checking throughout the codebase.", + "nodeIds": [ + "file:src/types/api.ts" + ], + "layerId": "layer:types" + }, + { + "order": 4, + "title": "Database Client", + "description": "src/db/client.ts is a tiny but critical file: it creates a postgres.js connection from the DATABASE_URL env var and wraps it with Drizzle to produce the fully-typed `db` singleton that all service functions import. The schema is passed to drizzle() here, enabling relational query mode.", + "nodeIds": [ + "file:src/db/client.ts" + ], + "layerId": "layer:database" + }, + { + "order": 5, + "title": "Application CRUD Service", + "description": "src/services/application.service.ts contains the main business logic: listing applications with multi-filter, paginated queries (status CSV filter, company category, job source, skills match threshold, archive toggle, multi-column sort with NULLS LAST); create (auto-sets dateApplied for applied status); update (partial fields, forces dateApplied=null on status='unsubmitted'); archive/restore; delete. Every mutating operation calls recordHistory from history.service to capture a snapshot.", + "nodeIds": [ + "service:src/services/application.service.ts" + ], + "layerId": "layer:services" + }, + { + "order": 6, + "title": "History & Version Restore Service", + "description": "src/services/history.service.ts implements snapshot-based audit history. recordHistory captures the full current application state (via captureSnapshot) and inserts it into application_history with an auto-incrementing sequence number. listHistory retrieves paginated entries and computes field-level diffs between consecutive snapshots (computeFieldDiffs). restoreToVersion rewrites the application row and replaces interview stages from a historical snapshot, then records a restore_version history entry.", + "nodeIds": [ + "service:src/services/history.service.ts" + ], + "layerId": "layer:services" + }, + { + "order": 7, + "title": "Interview Stage Service", + "description": "src/services/interview-stage.service.ts manages the ordered list of interview process steps. All mutations verify that the stage belongs to the given application (AND predicate on stageId + applicationId), bump the parent application's updatedAt, and record a history event. The service supports create, partial update, delete, and list-by-application operations.", + "nodeIds": [ + "service:src/services/interview-stage.service.ts" + ], + "layerId": "layer:services" + }, + { + "order": 8, + "title": "Shared Data Transformers", + "description": "src/services/shared.ts provides the canonical DB-to-API transformation. toApplicationResponse maps a Drizzle application row plus its interview stages into an ApplicationResponse, normalising date fields to YYYY-MM-DD strings and sorting stages by order. formatDate and formatDateTime handle the string/Date duality that Drizzle date columns can produce.", + "nodeIds": [ + "file:src/services/shared.ts" + ], + "layerId": "layer:services" + }, + { + "order": 9, + "title": "Applications Router", + "description": "src/routes/applications.ts is the Hono sub-router for all /applications endpoints. It uses @hono/zod-validator to validate query params, path params, and JSON bodies before passing to service functions. Covers 12 endpoints: GET /, GET /:id, POST /, PATCH /:id, DELETE /:id, POST /:id/archive, POST /:id/restore, GET /:id/history, POST /:id/history/restore, POST /:id/interview-stages, PATCH /:id/interview-stages/:stageId, DELETE /:id/interview-stages/:stageId.", + "nodeIds": [ + "endpoint:src/routes/applications.ts" + ], + "layerId": "layer:routes" + }, + { + "order": 10, + "title": "Health Check & Configuration", + "description": "src/routes/health.ts is a trivial but useful endpoint returning {status:'ok', timestamp} for liveness probes. The configuration files (package.json, tsconfig.json, drizzle.config.ts) complete the picture: drizzle.config.ts sets schemaFilter:'svelte_hono' to prevent Drizzle Kit from touching other schemas in the shared app_tracker database.", + "nodeIds": [ + "endpoint:src/routes/health.ts", + "config:drizzle.config.ts", + "config:package.json", + "config:tsconfig.json" + ], + "layerId": "layer:routes" + } + ] +} \ No newline at end of file diff --git a/hono-api/.understand-anything/meta.json b/hono-api/.understand-anything/meta.json new file mode 100644 index 00000000..3f0738ff --- /dev/null +++ b/hono-api/.understand-anything/meta.json @@ -0,0 +1,34 @@ +{ + "version": "2.5.1", + "projectName": "job-tracker-hono-api", + "description": "Hono.js TypeScript REST API for the Job Application Tracker, using Drizzle ORM with PostgreSQL (svelte_hono schema). Serves as the backend for svelte-ui.", + "generatedAt": "2026-05-04T00:00:00.000Z", + "gitHash": "e5054096efec8165e7d4cfe11fc103f77ad2d98f", + "entryPoint": "src/index.ts", + "languages": [ + "TypeScript" + ], + "frameworks": [ + "Hono", + "Drizzle ORM", + "Zod" + ], + "filesAnalyzed": 14, + "stats": { + "totalNodes": 14, + "totalEdges": 24, + "totalLayers": 6, + "tourSteps": 10, + "nodeTypes": { + "file": 5, + "schema": 1, + "endpoint": 2, + "service": 3, + "config": 3 + }, + "edgeTypes": { + "imports": 23, + "references": 1 + } + } +} diff --git a/koa-api/.understand-anything/.understandignore b/koa-api/.understand-anything/.understandignore new file mode 100644 index 00000000..c7034f16 --- /dev/null +++ b/koa-api/.understand-anything/.understandignore @@ -0,0 +1,2 @@ +# .understandignore — patterns for files/dirs to exclude from analysis +# Built-in defaults (always excluded): node_modules/, .git/, dist/, build/, *.lock diff --git a/koa-api/.understand-anything/knowledge-graph.json b/koa-api/.understand-anything/knowledge-graph.json new file mode 100644 index 00000000..7f287ca7 --- /dev/null +++ b/koa-api/.understand-anything/knowledge-graph.json @@ -0,0 +1,696 @@ +{ + "version": "1.0.0", + "project": { + "name": "koa-pg-api", + "languages": [ + "TypeScript", + "SQL" + ], + "frameworks": [ + "Koa", + "Zod" + ], + "description": "Koa.js REST API for the Job Application Tracker, using raw pg (PostgreSQL) for database access with schema isolation in the react_koa schema", + "analyzedAt": "2026-05-04T21:09:02.360118Z", + "gitCommitHash": "e5054096efec8165e7d4cfe11fc103f77ad2d98f" + }, + "nodes": [ + { + "id": "file:src/index.ts", + "type": "file", + "name": "index.ts", + "filePath": "src/index.ts", + "summary": "Application entry point. Creates and configures the Koa application: registers CORS, body parser, logger, and error handler middleware, mounts the applications and interview-stages routers, adds a /health endpoint, and starts the HTTP server on PORT (default 5010).", + "tags": [ + "entrypoint", + "server", + "koa", + "middleware" + ], + "complexity": "simple" + }, + { + "id": "file:src/db/client.ts", + "type": "file", + "name": "client.ts", + "filePath": "src/db/client.ts", + "summary": "PostgreSQL connection pool setup using the `pg` library. Exports a Pool instance configured for the react_koa schema, a `query` helper with timing logs, and a `withTransaction` helper that wraps operations in BEGIN/COMMIT/ROLLBACK.", + "tags": [ + "database", + "postgresql", + "connection-pool", + "transactions" + ], + "complexity": "simple" + }, + { + "id": "file:src/db/migrate.ts", + "type": "file", + "name": "migrate.ts", + "filePath": "src/db/migrate.ts", + "summary": "Database migration script. Reads schema.sql from disk and executes it against the PostgreSQL database to create tables, indexes, and triggers. Run as a standalone script via `npm run db:migrate`.", + "tags": [ + "migration", + "database", + "script" + ], + "complexity": "simple" + }, + { + "id": "file:src/db/schema.sql", + "type": "file", + "name": "schema.sql", + "filePath": "src/db/schema.sql", + "summary": "SQL DDL for the react_koa schema. Defines three enum types (application_status, company_category, job_source), three tables (applications, interview_stages, application_history), performance indexes, and an update_updated_at_column trigger function.", + "tags": [ + "sql", + "schema", + "database", + "ddl" + ], + "complexity": "moderate" + }, + { + "id": "file:src/db/seed.ts", + "type": "file", + "name": "seed.ts", + "filePath": "src/db/seed.ts", + "summary": "Database seed script. Inserts five sample job applications with varied statuses and adds interview stages for applications in the interviewing/offer/no-offer states. Run standalone via `npm run db:seed`.", + "tags": [ + "seed", + "database", + "script", + "fixtures" + ], + "complexity": "simple" + }, + { + "id": "file:src/middleware/errorHandler.ts", + "type": "file", + "name": "errorHandler.ts", + "filePath": "src/middleware/errorHandler.ts", + "summary": "Koa error-handling middleware. Defines the `AppError` class (with code, statusCode, details) and the `errorHandler` middleware that catches AppError (structured response), ZodError (validation_error 400), and generic errors (500 internal_error).", + "tags": [ + "middleware", + "error-handling", + "koa", + "zod" + ], + "complexity": "simple" + }, + { + "id": "file:src/middleware/logger.ts", + "type": "file", + "name": "logger.ts", + "filePath": "src/middleware/logger.ts", + "summary": "Koa logging middleware. Measures request duration and logs `METHOD URL - STATUS - Nms` after each request completes.", + "tags": [ + "middleware", + "logging", + "koa" + ], + "complexity": "simple" + }, + { + "id": "file:src/routes/applications.ts", + "type": "file", + "name": "applications.ts", + "filePath": "src/routes/applications.ts", + "summary": "Koa Router for /applications. Exposes: GET / (list with filters/pagination), POST / (create), GET /:id, PATCH /:id, DELETE /:id, POST /:id/archive, POST /:id/restore, GET /:id/history, POST /:id/history/restore. Delegates business logic to applicationService and history functions.", + "tags": [ + "router", + "rest", + "applications", + "koa", + "crud" + ], + "complexity": "moderate" + }, + { + "id": "file:src/routes/interview-stages.ts", + "type": "file", + "name": "interview-stages.ts", + "filePath": "src/routes/interview-stages.ts", + "summary": "Koa Router for /applications/:id/interview-stages. Exposes: POST / (create stage), PATCH /:stageId (update stage), DELETE /:stageId (delete stage). Delegates to interviewStageService.", + "tags": [ + "router", + "rest", + "interview-stages", + "koa", + "crud" + ], + "complexity": "simple" + }, + { + "id": "file:src/services/applications.service.ts", + "type": "file", + "name": "applications.service.ts", + "filePath": "src/services/applications.service.ts", + "summary": "ApplicationService class implementing CRUD for job applications. Handles listApplications (filters, pagination, sort), getApplication (with eager-loaded stages), createApplication, updateApplication (dynamic field mapping, unsubmitted-status date clearing), deleteApplication, archiveApplication, restoreApplication. Records history on every mutation.", + "tags": [ + "service", + "applications", + "crud", + "business-logic", + "history" + ], + "complexity": "complex" + }, + { + "id": "file:src/services/history.service.ts", + "type": "file", + "name": "history.service.ts", + "filePath": "src/services/history.service.ts", + "summary": "History service for snapshot-based application audit trail. Exports: captureSnapshot, getNextSequence, recordHistory (inserts history row), listHistory (paginated, with field-diff computation), restoreToVersion (transactional restore of application + stages), computeFieldDiffs, buildDescription, and FIELD_LABELS.", + "tags": [ + "service", + "history", + "audit-trail", + "snapshot", + "diff" + ], + "complexity": "complex" + }, + { + "id": "file:src/services/stages.service.ts", + "type": "file", + "name": "stages.service.ts", + "filePath": "src/services/stages.service.ts", + "summary": "InterviewStageService class. Handles createStage (with application existence check, touch parent updated_at, record history), updateStage (dynamic update, touch parent, record history), deleteStage (capture history before delete, touch parent), and getStagesByApplicationId.", + "tags": [ + "service", + "interview-stages", + "crud", + "business-logic" + ], + "complexity": "moderate" + }, + { + "id": "file:src/types/index.ts", + "type": "file", + "name": "index.ts", + "filePath": "src/types/index.ts", + "summary": "Central type definitions and Zod validation schemas. Defines enums (ApplicationStatus, CompanyCategory, JobSource), request schemas (CreateApplicationSchema, UpdateApplicationSchema, CreateInterviewStageSchema, UpdateInterviewStageSchema, ListApplicationsQuerySchema), domain interfaces (Application, InterviewStage, PaginatedApplications), history types (FieldChange, HistoryEntry, PaginatedHistory), and RestoreRequestSchema.", + "tags": [ + "types", + "zod", + "validation", + "schemas", + "domain-model" + ], + "complexity": "moderate" + }, + { + "id": "file:src/types/index.test.ts", + "type": "file", + "name": "index.test.ts", + "filePath": "src/types/index.test.ts", + "summary": "Jest unit tests for type schemas. Tests ApplicationStatusSchema (valid/invalid values, enum ordering), CreateApplicationSchema (dateApplied optional for unsubmitted), and UpdateApplicationSchema (unsubmitted status acceptance).", + "tags": [ + "tests", + "jest", + "validation", + "unit-test" + ], + "complexity": "simple" + }, + { + "id": "config:package.json", + "type": "config", + "name": "package.json", + "filePath": "package.json", + "summary": "npm package manifest for koa-pg-api. Defines scripts (dev, build, start, test, lint, format, db:migrate, db:seed), production dependencies (koa, @koa/router, @koa/cors, koa-bodyparser, pg, uuid, zod, dotenv), and devDependencies (TypeScript, ts-jest, jest, eslint, prettier, tsx, supertest).", + "tags": [ + "config", + "npm", + "dependencies", + "scripts" + ], + "complexity": "simple" + }, + { + "id": "config:tsconfig.json", + "type": "config", + "name": "tsconfig.json", + "filePath": "tsconfig.json", + "summary": "TypeScript compiler configuration. Targets ES2022/ESNext modules, strict mode enabled, outputs to dist/, includes src/**/* and excludes test files and dist.", + "tags": [ + "config", + "typescript", + "compiler" + ], + "complexity": "simple" + }, + { + "id": "config:eslint.config.js", + "type": "config", + "name": "eslint.config.js", + "filePath": "eslint.config.js", + "summary": "ESLint flat configuration using typescript-eslint. Enables recommended JS and TS rules, enforces no-explicit-any and explicit-function-return-type, ignores dist and node_modules.", + "tags": [ + "config", + "eslint", + "linting", + "typescript" + ], + "complexity": "simple" + }, + { + "id": "config:jest.config.ts", + "type": "config", + "name": "jest.config.ts", + "filePath": "jest.config.ts", + "summary": "Jest test runner configuration. Sets node test environment and uses ts-jest for TypeScript transformation.", + "tags": [ + "config", + "jest", + "testing" + ], + "complexity": "simple" + }, + { + "id": "config:.auditconfig.json", + "type": "config", + "name": ".auditconfig.json", + "filePath": ".auditconfig.json", + "summary": "audit-ci configuration. Empty allowlist; blocks on high-severity vulnerabilities using auto package-manager detection.", + "tags": [ + "config", + "security", + "audit" + ], + "complexity": "simple" + } + ], + "edges": [ + { + "source": "file:src/index.ts", + "target": "file:src/middleware/logger.ts", + "type": "imports", + "weight": 0.7 + }, + { + "source": "file:src/index.ts", + "target": "file:src/middleware/errorHandler.ts", + "type": "imports", + "weight": 0.7 + }, + { + "source": "file:src/index.ts", + "target": "file:src/routes/applications.ts", + "type": "imports", + "weight": 0.7 + }, + { + "source": "file:src/index.ts", + "target": "file:src/routes/interview-stages.ts", + "type": "imports", + "weight": 0.7 + }, + { + "source": "file:src/db/migrate.ts", + "target": "file:src/db/client.ts", + "type": "imports", + "weight": 0.7 + }, + { + "source": "file:src/db/migrate.ts", + "target": "file:src/db/schema.sql", + "type": "reads_from", + "weight": 0.5 + }, + { + "source": "file:src/db/seed.ts", + "target": "file:src/db/client.ts", + "type": "imports", + "weight": 0.7 + }, + { + "source": "file:src/middleware/errorHandler.ts", + "target": "file:src/types/index.ts", + "type": "imports", + "weight": 0.7 + }, + { + "source": "file:src/routes/applications.ts", + "target": "file:src/services/applications.service.ts", + "type": "imports", + "weight": 0.7 + }, + { + "source": "file:src/routes/applications.ts", + "target": "file:src/services/history.service.ts", + "type": "imports", + "weight": 0.7 + }, + { + "source": "file:src/routes/applications.ts", + "target": "file:src/types/index.ts", + "type": "imports", + "weight": 0.7 + }, + { + "source": "file:src/routes/interview-stages.ts", + "target": "file:src/services/stages.service.ts", + "type": "imports", + "weight": 0.7 + }, + { + "source": "file:src/routes/interview-stages.ts", + "target": "file:src/types/index.ts", + "type": "imports", + "weight": 0.7 + }, + { + "source": "file:src/services/applications.service.ts", + "target": "file:src/db/client.ts", + "type": "imports", + "weight": 0.7 + }, + { + "source": "file:src/services/applications.service.ts", + "target": "file:src/middleware/errorHandler.ts", + "type": "imports", + "weight": 0.7 + }, + { + "source": "file:src/services/applications.service.ts", + "target": "file:src/types/index.ts", + "type": "imports", + "weight": 0.7 + }, + { + "source": "file:src/services/applications.service.ts", + "target": "file:src/services/history.service.ts", + "type": "imports", + "weight": 0.7 + }, + { + "source": "file:src/services/history.service.ts", + "target": "file:src/db/client.ts", + "type": "imports", + "weight": 0.7 + }, + { + "source": "file:src/services/history.service.ts", + "target": "file:src/types/index.ts", + "type": "imports", + "weight": 0.7 + }, + { + "source": "file:src/services/stages.service.ts", + "target": "file:src/db/client.ts", + "type": "imports", + "weight": 0.7 + }, + { + "source": "file:src/services/stages.service.ts", + "target": "file:src/middleware/errorHandler.ts", + "type": "imports", + "weight": 0.7 + }, + { + "source": "file:src/services/stages.service.ts", + "target": "file:src/types/index.ts", + "type": "imports", + "weight": 0.7 + }, + { + "source": "file:src/services/stages.service.ts", + "target": "file:src/services/history.service.ts", + "type": "imports", + "weight": 0.7 + }, + { + "source": "file:src/types/index.test.ts", + "target": "file:src/types/index.ts", + "type": "imports", + "weight": 0.7 + }, + { + "source": "file:src/types/index.test.ts", + "target": "file:src/types/index.ts", + "type": "tested_by", + "weight": 0.5 + }, + { + "source": "config:package.json", + "target": "file:src/index.ts", + "type": "configures", + "weight": 0.6 + }, + { + "source": "config:tsconfig.json", + "target": "file:src/index.ts", + "type": "configures", + "weight": 0.6 + }, + { + "source": "config:jest.config.ts", + "target": "file:src/types/index.test.ts", + "type": "configures", + "weight": 0.6 + }, + { + "source": "config:eslint.config.js", + "target": "file:src/index.ts", + "type": "configures", + "weight": 0.6 + }, + { + "source": "file:src/routes/applications.ts", + "target": "file:src/services/applications.service.ts", + "type": "calls", + "weight": 0.8 + }, + { + "source": "file:src/routes/applications.ts", + "target": "file:src/services/history.service.ts", + "type": "calls", + "weight": 0.8 + }, + { + "source": "file:src/routes/interview-stages.ts", + "target": "file:src/services/stages.service.ts", + "type": "calls", + "weight": 0.8 + }, + { + "source": "file:src/services/applications.service.ts", + "target": "file:src/services/history.service.ts", + "type": "calls", + "weight": 0.8 + }, + { + "source": "file:src/services/stages.service.ts", + "target": "file:src/services/history.service.ts", + "type": "calls", + "weight": 0.8 + }, + { + "source": "file:src/services/applications.service.ts", + "target": "file:src/db/client.ts", + "type": "reads_from", + "weight": 0.5 + }, + { + "source": "file:src/services/history.service.ts", + "target": "file:src/db/client.ts", + "type": "reads_from", + "weight": 0.5 + }, + { + "source": "file:src/services/stages.service.ts", + "target": "file:src/db/client.ts", + "type": "reads_from", + "weight": 0.5 + }, + { + "source": "file:src/services/applications.service.ts", + "target": "file:src/db/schema.sql", + "type": "reads_from", + "weight": 0.5 + }, + { + "source": "file:src/services/history.service.ts", + "target": "file:src/db/schema.sql", + "type": "reads_from", + "weight": 0.5 + }, + { + "source": "file:src/services/stages.service.ts", + "target": "file:src/db/schema.sql", + "type": "reads_from", + "weight": 0.5 + }, + { + "source": "file:src/types/index.ts", + "target": "file:src/db/schema.sql", + "type": "related", + "weight": 0.5 + }, + { + "source": "file:src/db/client.ts", + "target": "file:src/db/schema.sql", + "type": "reads_from", + "weight": 0.5 + }, + { + "source": "config:.auditconfig.json", + "target": "config:package.json", + "type": "configures", + "weight": 0.6 + } + ], + "layers": [ + { + "id": "layer:configuration", + "name": "Configuration", + "description": "Project configuration files: package manifest, TypeScript compiler settings, ESLint rules, Jest test runner config, and security audit configuration.", + "nodeIds": [ + "config:package.json", + "config:tsconfig.json", + "config:eslint.config.js", + "config:jest.config.ts", + "config:.auditconfig.json" + ] + }, + { + "id": "layer:database", + "name": "Database", + "description": "Database schema definition, connection pool, migration runner, and seed scripts. The schema.sql defines all tables, enums, indexes, and triggers in the react_koa PostgreSQL schema.", + "nodeIds": [ + "file:src/db/schema.sql", + "file:src/db/client.ts", + "file:src/db/migrate.ts", + "file:src/db/seed.ts" + ] + }, + { + "id": "layer:types-and-validation", + "name": "Types & Validation", + "description": "Shared TypeScript interfaces, Zod validation schemas, and domain model types used across the entire application. Single source of truth for request/response contracts.", + "nodeIds": [ + "file:src/types/index.ts", + "file:src/types/index.test.ts" + ] + }, + { + "id": "layer:middleware", + "name": "Middleware", + "description": "Koa middleware components: structured error handling with typed AppError class and Zod error mapping, plus request/response logging.", + "nodeIds": [ + "file:src/middleware/errorHandler.ts", + "file:src/middleware/logger.ts" + ] + }, + { + "id": "layer:services", + "name": "Services", + "description": "Business logic layer. ApplicationService manages CRUD with history recording. InterviewStageService manages stage lifecycle. HistoryService provides snapshot-based audit trail with field-diff computation and version restore.", + "nodeIds": [ + "file:src/services/applications.service.ts", + "file:src/services/history.service.ts", + "file:src/services/stages.service.ts" + ] + }, + { + "id": "layer:routes", + "name": "Routes", + "description": "HTTP routing layer built on @koa/router. Maps REST endpoints to service calls, handles request parsing with Zod schemas, and sets appropriate HTTP status codes.", + "nodeIds": [ + "file:src/routes/applications.ts", + "file:src/routes/interview-stages.ts" + ] + }, + { + "id": "layer:application-entry", + "name": "Application Entry", + "description": "Koa application bootstrap: assembles middleware stack and mounts routers. The single entry point that wires all layers together and starts the HTTP server.", + "nodeIds": [ + "file:src/index.ts" + ] + } + ], + "tour": [ + { + "order": 1, + "title": "Project Configuration", + "description": "Start with `package.json` to understand what this project is: a Koa.js REST API for a job application tracker using raw PostgreSQL via `pg`. Note the scripts \u2014 `db:migrate` and `db:seed` for DB setup, `dev` for hot-reload with tsx, and `test` via Jest. `tsconfig.json` establishes strict TypeScript targeting ES2022 with ESNext modules.", + "nodeIds": [ + "config:package.json", + "config:tsconfig.json" + ] + }, + { + "order": 2, + "title": "Application Entry Point", + "description": "`src/index.ts` is where it all starts. It creates a Koa app, attaches four middleware (cors, bodyParser, logger, errorHandler), adds a `/health` endpoint, mounts the two routers, and starts listening on port 5010. Reading this file gives you the complete request pipeline at a glance.", + "nodeIds": [ + "file:src/index.ts" + ] + }, + { + "order": 3, + "title": "Database Schema & Connection", + "description": "`schema.sql` defines the entire data model: three PostgreSQL enum types, three tables (applications, interview_stages, application_history), eight performance indexes, and an auto-updating `updated_at` trigger. `client.ts` wraps a pg Pool configured to use the `react_koa` schema \u2014 every query goes through the exported `query()` helper or `withTransaction()` for multi-step operations.", + "nodeIds": [ + "file:src/db/schema.sql", + "file:src/db/client.ts" + ] + }, + { + "order": 4, + "title": "Types & Validation Schemas", + "description": "`src/types/index.ts` is the single source of truth for all domain types and validation. It defines Zod schemas for request bodies and query parameters (CreateApplicationSchema, UpdateApplicationSchema, ListApplicationsQuerySchema etc.) and TypeScript interfaces for the domain model (Application, InterviewStage, PaginatedApplications, HistoryEntry). All services and routes import from here.", + "nodeIds": [ + "file:src/types/index.ts", + "file:src/types/index.test.ts" + ] + }, + { + "order": 5, + "title": "Middleware Layer", + "description": "Two middleware functions handle cross-cutting concerns. `errorHandler.ts` defines the `AppError` class and catches all errors \u2014 mapping AppError to structured JSON, ZodError to 400 validation responses, and everything else to 500. `logger.ts` measures and logs request duration. Both are applied in `index.ts` before the routers.", + "nodeIds": [ + "file:src/middleware/errorHandler.ts", + "file:src/middleware/logger.ts" + ] + }, + { + "order": 6, + "title": "History Service \u2014 Audit Trail", + "description": "`history.service.ts` is the most sophisticated module. It powers the snapshot-based audit trail: `captureSnapshot` fetches the full application state (including stages), `recordHistory` stores a numbered snapshot row, `listHistory` returns paginated entries with field-level diffs via `computeFieldDiffs`, and `restoreToVersion` transactionally restores both the application row and its stages to a past snapshot.", + "nodeIds": [ + "file:src/services/history.service.ts" + ] + }, + { + "order": 7, + "title": "Application & Stage Services", + "description": "`applications.service.ts` handles full CRUD with filtering, sorting, and pagination. Every mutation calls `recordHistory`. `stages.service.ts` manages interview stage lifecycle \u2014 create, update, delete \u2014 each touching the parent application's `updated_at` and recording a history event. Both services use dynamic SQL builders to support partial updates.", + "nodeIds": [ + "file:src/services/applications.service.ts", + "file:src/services/stages.service.ts" + ] + }, + { + "order": 8, + "title": "REST Routes", + "description": "`routes/applications.ts` exposes 9 endpoints under `/applications` including history listing and version restore. `routes/interview-stages.ts` exposes 3 endpoints under `/applications/:id/interview-stages`. Both use Zod `.parse()` on incoming data before handing off to services \u2014 validation failures are caught by errorHandler.", + "nodeIds": [ + "file:src/routes/applications.ts", + "file:src/routes/interview-stages.ts" + ] + }, + { + "order": 9, + "title": "Database Migration & Seeding", + "description": "`migrate.ts` reads `schema.sql` and executes it as a standalone script \u2014 run once to set up the database. `seed.ts` inserts five sample applications with realistic statuses and interview stages for development. Both scripts connect using the same pool from `client.ts` and close the pool when done.", + "nodeIds": [ + "file:src/db/migrate.ts", + "file:src/db/seed.ts" + ] + } + ] +} \ No newline at end of file diff --git a/koa-api/.understand-anything/meta.json b/koa-api/.understand-anything/meta.json new file mode 100644 index 00000000..c6813e31 --- /dev/null +++ b/koa-api/.understand-anything/meta.json @@ -0,0 +1,6 @@ +{ + "lastAnalyzedAt": "2026-05-04T00:00:00Z", + "gitCommitHash": "e5054096efec8165e7d4cfe11fc103f77ad2d98f", + "version": "1.0.0", + "analyzedFiles": 19 +} diff --git a/lambda-api/.understand-anything/.understandignore b/lambda-api/.understand-anything/.understandignore new file mode 100644 index 00000000..6e7334a3 --- /dev/null +++ b/lambda-api/.understand-anything/.understandignore @@ -0,0 +1,8 @@ +# .understandignore — patterns for files/dirs to exclude from analysis +# Built-in defaults (always excluded): node_modules/, .git/, dist/, build/, *.lock + +# Exclude env files with secrets +.env + +# Exclude generated CDK output +cdk.out/ diff --git a/lambda-api/.understand-anything/knowledge-graph.json b/lambda-api/.understand-anything/knowledge-graph.json new file mode 100644 index 00000000..c5f9839a --- /dev/null +++ b/lambda-api/.understand-anything/knowledge-graph.json @@ -0,0 +1,1331 @@ +{ + "version": "1.0.0", + "project": { + "name": "job-tracker-lambda-api", + "languages": [ + "javascript", + "json", + "markdown", + "typescript" + ], + "frameworks": [ + "AWS CDK", + "DynamoDB", + "Hono", + "Vitest" + ], + "description": "TypeScript REST API deployed as AWS Lambda with Hono, DynamoDB single-table design, and a CDK sub-project for infrastructure. Runs as a local Hono dev server on port 5090.", + "analyzedAt": "2026-05-04T14:36:15.870858+00:00", + "gitCommitHash": "e5054096efec8165e7d4cfe11fc103f77ad2d98f" + }, + "nodes": [ + { + "id": "config:.auditconfig.json", + "type": "config", + "name": ".auditconfig.json", + "filePath": ".auditconfig.json", + "summary": "audit-ci configuration allowing high-severity advisories for the lambda-api package, using the npm package manager.", + "tags": [ + "configuration", + "security", + "build-system" + ], + "complexity": "simple" + }, + { + "id": "config:.env.example", + "type": "config", + "name": ".env.example", + "filePath": ".env.example", + "summary": "Environment variable template defining DynamoDB endpoint, table name, AWS credentials, and server port required to run the lambda-api locally.", + "tags": [ + "configuration", + "infrastructure", + "deployment" + ], + "complexity": "simple" + }, + { + "id": "document:CLAUDE.md", + "type": "document", + "name": "CLAUDE.md", + "filePath": "CLAUDE.md", + "summary": "Stack-specific guidance for the lambda-api covering DynamoDB single-table design, local development patterns, CDK deployment patterns, and common gotchas for the Hono+Lambda+DynamoDB stack.", + "tags": [ + "documentation", + "configuration", + "infrastructure" + ], + "complexity": "moderate" + }, + { + "id": "config:cdk/.auditconfig.json", + "type": "config", + "name": ".auditconfig.json", + "filePath": "cdk/.auditconfig.json", + "summary": "audit-ci configuration for the CDK sub-package, allowing moderate-severity advisories.", + "tags": [ + "configuration", + "security", + "build-system" + ], + "complexity": "simple" + }, + { + "id": "file:cdk/bin/app.ts", + "type": "file", + "name": "app.ts", + "filePath": "cdk/bin/app.ts", + "summary": "CDK application entry point that instantiates the LambdaApiStack, configuring synthesizer and environment from CDK context.", + "tags": [ + "entry-point", + "infrastructure", + "deployment" + ], + "complexity": "simple" + }, + { + "id": "config:cdk/cdk.json", + "type": "config", + "name": "cdk.json", + "filePath": "cdk/cdk.json", + "summary": "CDK toolkit configuration specifying the app entry point, watch include/exclude patterns, and feature flag context for the lambda-api stack.", + "tags": [ + "configuration", + "infrastructure", + "build-system" + ], + "complexity": "simple" + }, + { + "id": "file:cdk/lib/lambda-api-stack.ts", + "type": "file", + "name": "lambda-api-stack.ts", + "filePath": "cdk/lib/lambda-api-stack.ts", + "summary": "AWS CDK stack defining the DynamoDB TableV2 with GSI indexes, NodejsFunction Lambda, and HTTP API Gateway v2 integration for deploying the lambda-api to AWS.", + "tags": [ + "infrastructure", + "deployment", + "service" + ], + "complexity": "moderate", + "languageNotes": "Uses TableV2 which synthesizes to AWS::DynamoDB::GlobalTable (not AWS::DynamoDB::Table), and NodejsFunction which bundles via esbuild at synth time." + }, + { + "id": "class:cdk/lib/lambda-api-stack.ts:LambdaApiStack", + "type": "class", + "name": "LambdaApiStack", + "filePath": "cdk/lib/lambda-api-stack.ts", + "lineRange": [ + 10, + 78 + ], + "summary": "CDK Stack subclass that provisions the DynamoDB table, Lambda function, and HTTP API Gateway, wiring them together with IAM permissions.", + "tags": [ + "infrastructure", + "deployment", + "service" + ], + "complexity": "moderate" + }, + { + "id": "config:cdk/package.json", + "type": "config", + "name": "package.json", + "filePath": "cdk/package.json", + "summary": "Package manifest for the CDK sub-project, defining scripts for synth, deploy, and diff, plus CDK v2, esbuild, and vitest dependencies.", + "tags": [ + "configuration", + "build-system", + "infrastructure" + ], + "complexity": "simple" + }, + { + "id": "file:cdk/test/lambda-api-stack.test.ts", + "type": "file", + "name": "lambda-api-stack.test.ts", + "filePath": "cdk/test/lambda-api-stack.test.ts", + "summary": "Vitest test suite for the LambdaApiStack CDK construct, asserting that the synthesized CloudFormation template contains expected DynamoDB GlobalTable, Lambda, and HTTP API resources.", + "tags": [ + "test", + "infrastructure", + "deployment" + ], + "complexity": "moderate" + }, + { + "id": "config:cdk/tsconfig.json", + "type": "config", + "name": "tsconfig.json", + "filePath": "cdk/tsconfig.json", + "summary": "TypeScript compiler configuration for the CDK sub-package using CommonJS modules and node module resolution, intentionally different from the parent lambda-api ESM config.", + "tags": [ + "configuration", + "build-system", + "typescript" + ], + "complexity": "simple", + "languageNotes": "Must use module:CommonJS because ts-node (the CDK runner) requires CJS; cannot share the parent lambda-api tsconfig.json." + }, + { + "id": "file:cdk/vitest.config.ts", + "type": "file", + "name": "vitest.config.ts", + "filePath": "cdk/vitest.config.ts", + "summary": "Vitest configuration for the CDK sub-package, scoped to include only the cdk/test/ directory test files.", + "tags": [ + "configuration", + "test", + "build-system" + ], + "complexity": "simple" + }, + { + "id": "file:eslint.config.js", + "type": "file", + "name": "eslint.config.js", + "filePath": "eslint.config.js", + "summary": "ESLint flat configuration for the lambda-api package, applying TypeScript ESLint rules to source files.", + "tags": [ + "configuration", + "build-system" + ], + "complexity": "simple" + }, + { + "id": "config:package.json", + "type": "config", + "name": "package.json", + "filePath": "package.json", + "summary": "Root package manifest for the lambda-api package defining build, dev, test, and migration scripts alongside Hono, DynamoDB, and Zod runtime dependencies.", + "tags": [ + "configuration", + "build-system", + "entry-point" + ], + "complexity": "moderate" + }, + { + "id": "file:scripts/setup-dynamodb.ts", + "type": "file", + "name": "setup-dynamodb.ts", + "filePath": "scripts/setup-dynamodb.ts", + "summary": "Idempotent DynamoDB table setup script that creates the lambda_api_applications table with its GSI indexes if it does not already exist, used by the migrate:lambda-api npm script.", + "tags": [ + "script", + "database", + "infrastructure" + ], + "complexity": "moderate" + }, + { + "id": "function:scripts/setup-dynamodb.ts:tableExists", + "type": "function", + "name": "tableExists", + "filePath": "scripts/setup-dynamodb.ts", + "lineRange": [ + 37, + 48 + ], + "summary": "Checks whether the DynamoDB table already exists by sending a DescribeTableCommand and catching the ResourceNotFoundException.", + "tags": [ + "database", + "utility" + ], + "complexity": "simple" + }, + { + "id": "function:scripts/setup-dynamodb.ts:createTable", + "type": "function", + "name": "createTable", + "filePath": "scripts/setup-dynamodb.ts", + "lineRange": [ + 50, + 87 + ], + "summary": "Creates the DynamoDB table with partition key, sort key, GSI1, and GSI2 index definitions using CreateTableCommand.", + "tags": [ + "database", + "infrastructure" + ], + "complexity": "moderate" + }, + { + "id": "file:src/__tests__/csv.test.ts", + "type": "file", + "name": "csv.test.ts", + "filePath": "src/__tests__/csv.test.ts", + "summary": "Unit tests for the CSV service, covering parsing, serialization, and import logic for job application data.", + "tags": [ + "test", + "serialization" + ], + "complexity": "moderate" + }, + { + "id": "file:src/__tests__/unit.test.ts", + "type": "file", + "name": "unit.test.ts", + "filePath": "src/__tests__/unit.test.ts", + "summary": "Comprehensive unit tests for application service functions including CRUD operations, pagination cursor encoding/decoding, and field validation logic.", + "tags": [ + "test", + "utility" + ], + "complexity": "complex" + }, + { + "id": "file:src/app.ts", + "type": "file", + "name": "app.ts", + "filePath": "src/app.ts", + "summary": "Hono application factory that configures CORS, mounts the applications and health routes, and registers global error handlers; exported as the shared app instance for both server and Lambda handler.", + "tags": [ + "entry-point", + "api-handler", + "middleware" + ], + "complexity": "moderate" + }, + { + "id": "file:src/handler.ts", + "type": "file", + "name": "handler.ts", + "filePath": "src/handler.ts", + "summary": "AWS Lambda handler entry point that wraps the shared Hono app using hono/aws-lambda's handle() adapter for Lambda invocations.", + "tags": [ + "entry-point", + "api-handler", + "service" + ], + "complexity": "simple" + }, + { + "id": "file:src/routes/applications.ts", + "type": "file", + "name": "applications.ts", + "filePath": "src/routes/applications.ts", + "summary": "Hono router defining all application REST endpoints including CRUD, archive/restore, interview stage management, history, and CSV import/export, with Zod request validation.", + "tags": [ + "api-handler", + "service", + "validation" + ], + "complexity": "complex" + }, + { + "id": "file:src/routes/health.ts", + "type": "file", + "name": "health.ts", + "filePath": "src/routes/health.ts", + "summary": "Hono router providing a simple GET /health endpoint that returns a 200 OK status response.", + "tags": [ + "api-handler", + "service" + ], + "complexity": "simple" + }, + { + "id": "file:src/server.ts", + "type": "file", + "name": "server.ts", + "filePath": "src/server.ts", + "summary": "Local development server entry point that starts the Hono app on a configurable port using @hono/node-server.", + "tags": [ + "entry-point", + "service" + ], + "complexity": "simple" + }, + { + "id": "file:src/services/application.service.ts", + "type": "file", + "name": "application.service.ts", + "filePath": "src/services/application.service.ts", + "summary": "Core application service implementing all DynamoDB CRUD operations for job applications, including list with filtering/pagination, archive/restore, and point-in-time version restore from history.", + "tags": [ + "service", + "data-model", + "api-handler" + ], + "complexity": "complex" + }, + { + "id": "function:src/services/application.service.ts:listApplications", + "type": "function", + "name": "listApplications", + "filePath": "src/services/application.service.ts", + "lineRange": [ + 168, + 308 + ], + "summary": "Queries DynamoDB using GSI1 or GSI2 with dynamic FilterExpression, applies in-memory sorting and offset-based or cursor-based pagination, then fetches associated stages for each page.", + "tags": [ + "service", + "data-model", + "api-handler" + ], + "complexity": "complex" + }, + { + "id": "function:src/services/application.service.ts:createApplication", + "type": "function", + "name": "createApplication", + "filePath": "src/services/application.service.ts", + "lineRange": [ + 310, + 353 + ], + "summary": "Creates a new job application item in DynamoDB with UUID, timestamps, and GSI key projections, then records a creation history entry.", + "tags": [ + "service", + "data-model" + ], + "complexity": "moderate" + }, + { + "id": "function:src/services/application.service.ts:updateApplication", + "type": "function", + "name": "updateApplication", + "filePath": "src/services/application.service.ts", + "lineRange": [ + 355, + 411 + ], + "summary": "Applies partial field updates to a DynamoDB application item, rebuilds GSI keys for changed status/archived fields, and records a field-level change history entry.", + "tags": [ + "service", + "data-model" + ], + "complexity": "complex" + }, + { + "id": "function:src/services/application.service.ts:deleteApplication", + "type": "function", + "name": "deleteApplication", + "filePath": "src/services/application.service.ts", + "lineRange": [ + 413, + 443 + ], + "summary": "Cascade-deletes an application and all related stage and history items by querying on PK then batching individual DeleteCommands.", + "tags": [ + "service", + "data-model" + ], + "complexity": "moderate" + }, + { + "id": "function:src/services/application.service.ts:restoreToVersion", + "type": "function", + "name": "restoreToVersion", + "filePath": "src/services/application.service.ts", + "lineRange": [ + 494, + 593 + ], + "summary": "Restores an application to a historical snapshot by reading the HIST# item, rebuilding the application and stage items, replacing all current stages via BatchWrite, and recording a restore history entry.", + "tags": [ + "service", + "data-model" + ], + "complexity": "complex" + }, + { + "id": "file:src/services/csv.service.ts", + "type": "file", + "name": "csv.service.ts", + "filePath": "src/services/csv.service.ts", + "summary": "CSV import and export service for job applications, implementing a character-by-character quoted-field parser, Zod enum validation, and deduplication by job posting URL.", + "tags": [ + "service", + "serialization", + "data-model" + ], + "complexity": "complex" + }, + { + "id": "function:src/services/csv.service.ts:parseCsvRows", + "type": "function", + "name": "parseCsvRows", + "filePath": "src/services/csv.service.ts", + "lineRange": [ + 45, + 93 + ], + "summary": "Character-by-character CSV parser that correctly handles quoted fields with embedded newlines, commas, and escaped double-quotes.", + "tags": [ + "serialization", + "utility" + ], + "complexity": "moderate" + }, + { + "id": "function:src/services/csv.service.ts:importApplications", + "type": "function", + "name": "importApplications", + "filePath": "src/services/csv.service.ts", + "lineRange": [ + 193, + 297 + ], + "summary": "Parses a CSV upload, validates each row against Zod schemas, deduplicates by job posting URL, and creates or updates application records via the application service.", + "tags": [ + "service", + "validation", + "data-model" + ], + "complexity": "complex" + }, + { + "id": "function:src/services/csv.service.ts:exportApplications", + "type": "function", + "name": "exportApplications", + "filePath": "src/services/csv.service.ts", + "lineRange": [ + 299, + 325 + ], + "summary": "Fetches all applications via listApplications with pagination loop and serializes them to CSV format.", + "tags": [ + "service", + "serialization" + ], + "complexity": "moderate" + }, + { + "id": "file:src/services/dynamodb.client.ts", + "type": "file", + "name": "dynamodb.client.ts", + "filePath": "src/services/dynamodb.client.ts", + "summary": "Singleton DynamoDB DocumentClient and TABLE_NAME constant exported for use by all service modules; reads DYNAMODB_ENDPOINT and AWS_REGION from environment at module load time.", + "tags": [ + "service", + "singleton", + "infrastructure" + ], + "complexity": "simple", + "languageNotes": "Must import 'dotenv/config' as its first line so DYNAMODB_ENDPOINT is available at module load time when running via tsx." + }, + { + "id": "file:src/services/history.service.ts", + "type": "file", + "name": "history.service.ts", + "filePath": "src/services/history.service.ts", + "summary": "History tracking service that records application change events as HIST# items in DynamoDB with atomic sequence counters, lists paginated history, and computes field-level diffs between snapshots.", + "tags": [ + "service", + "data-model", + "event-handler" + ], + "complexity": "complex" + }, + { + "id": "function:src/services/history.service.ts:recordHistory", + "type": "function", + "name": "recordHistory", + "filePath": "src/services/history.service.ts", + "lineRange": [ + 52, + 75 + ], + "summary": "Atomically increments the application's historySequence counter then writes a HIST# item capturing the action description and full application snapshot.", + "tags": [ + "service", + "data-model", + "event-handler" + ], + "complexity": "moderate" + }, + { + "id": "function:src/services/history.service.ts:computeFieldDiffs", + "type": "function", + "name": "computeFieldDiffs", + "filePath": "src/services/history.service.ts", + "lineRange": [ + 137, + 164 + ], + "summary": "Computes field-level before/after diffs between two application snapshots by comparing serialized JSON values for each known field.", + "tags": [ + "utility", + "data-model" + ], + "complexity": "moderate" + }, + { + "id": "file:src/services/interview-stage.service.ts", + "type": "file", + "name": "interview-stage.service.ts", + "filePath": "src/services/interview-stage.service.ts", + "summary": "Interview stage service providing CRUD operations for STAGE# DynamoDB items, calling touchApplication to update parent application timestamps and recording stage change history.", + "tags": [ + "service", + "data-model", + "api-handler" + ], + "complexity": "complex" + }, + { + "id": "function:src/services/interview-stage.service.ts:createInterviewStage", + "type": "function", + "name": "createInterviewStage", + "filePath": "src/services/interview-stage.service.ts", + "lineRange": [ + 46, + 77 + ], + "summary": "Creates a new STAGE# item in DynamoDB, touches the parent application's updatedAt, and records a stage-creation history entry.", + "tags": [ + "service", + "data-model" + ], + "complexity": "moderate" + }, + { + "id": "function:src/services/interview-stage.service.ts:deleteInterviewStage", + "type": "function", + "name": "deleteInterviewStage", + "filePath": "src/services/interview-stage.service.ts", + "lineRange": [ + 124, + 156 + ], + "summary": "Looks up and deletes a STAGE# item by application ID and stage ID, then touches the parent application and records a deletion history entry.", + "tags": [ + "service", + "data-model" + ], + "complexity": "moderate" + }, + { + "id": "file:src/types/api.ts", + "type": "file", + "name": "api.ts", + "filePath": "src/types/api.ts", + "summary": "Zod schema definitions for all API request and response types including application CRUD, interview stages, pagination shapes, history entries, and import results.", + "tags": [ + "type-definition", + "validation", + "api-handler" + ], + "complexity": "complex" + }, + { + "id": "file:src/types/dynamo.ts", + "type": "file", + "name": "dynamo.ts", + "filePath": "src/types/dynamo.ts", + "summary": "DynamoDB single-table type definitions and key builder functions for APP#, STAGE#, HIST# items, plus GSI key constructors and item type guards.", + "tags": [ + "type-definition", + "data-model", + "utility" + ], + "complexity": "moderate" + }, + { + "id": "config:tsconfig.json", + "type": "config", + "name": "tsconfig.json", + "filePath": "tsconfig.json", + "summary": "Root TypeScript compiler configuration for the lambda-api package using ESM modules, bundler module resolution, and strict mode targeting Node 20.", + "tags": [ + "configuration", + "build-system", + "typescript" + ], + "complexity": "simple" + }, + { + "id": "file:vitest.config.ts", + "type": "file", + "name": "vitest.config.ts", + "filePath": "vitest.config.ts", + "summary": "Vitest configuration for the lambda-api package, scoped to src/**/*.test.ts files.", + "tags": [ + "configuration", + "test", + "build-system" + ], + "complexity": "simple" + } + ], + "edges": [ + { + "source": "file:cdk/bin/app.ts", + "target": "file:cdk/lib/lambda-api-stack.ts", + "type": "imports", + "direction": "forward", + "weight": 0.7 + }, + { + "source": "file:cdk/test/lambda-api-stack.test.ts", + "target": "file:cdk/lib/lambda-api-stack.ts", + "type": "imports", + "direction": "forward", + "weight": 0.7 + }, + { + "source": "file:cdk/lib/lambda-api-stack.ts", + "target": "class:cdk/lib/lambda-api-stack.ts:LambdaApiStack", + "type": "contains", + "direction": "forward", + "weight": 1.0 + }, + { + "source": "file:cdk/lib/lambda-api-stack.ts", + "target": "class:cdk/lib/lambda-api-stack.ts:LambdaApiStack", + "type": "exports", + "direction": "forward", + "weight": 0.8 + }, + { + "source": "file:cdk/bin/app.ts", + "target": "class:cdk/lib/lambda-api-stack.ts:LambdaApiStack", + "type": "depends_on", + "direction": "forward", + "weight": 0.6 + }, + { + "source": "file:cdk/test/lambda-api-stack.test.ts", + "target": "class:cdk/lib/lambda-api-stack.ts:LambdaApiStack", + "type": "tested_by", + "direction": "forward", + "weight": 0.5 + }, + { + "source": "file:scripts/setup-dynamodb.ts", + "target": "function:scripts/setup-dynamodb.ts:tableExists", + "type": "contains", + "direction": "forward", + "weight": 1.0 + }, + { + "source": "file:scripts/setup-dynamodb.ts", + "target": "function:scripts/setup-dynamodb.ts:createTable", + "type": "contains", + "direction": "forward", + "weight": 1.0 + }, + { + "source": "file:src/services/application.service.ts", + "target": "function:src/services/application.service.ts:listApplications", + "type": "contains", + "direction": "forward", + "weight": 1.0 + }, + { + "source": "file:src/services/application.service.ts", + "target": "function:src/services/application.service.ts:createApplication", + "type": "contains", + "direction": "forward", + "weight": 1.0 + }, + { + "source": "file:src/services/application.service.ts", + "target": "function:src/services/application.service.ts:updateApplication", + "type": "contains", + "direction": "forward", + "weight": 1.0 + }, + { + "source": "file:src/services/application.service.ts", + "target": "function:src/services/application.service.ts:deleteApplication", + "type": "contains", + "direction": "forward", + "weight": 1.0 + }, + { + "source": "file:src/services/application.service.ts", + "target": "function:src/services/application.service.ts:restoreToVersion", + "type": "contains", + "direction": "forward", + "weight": 1.0 + }, + { + "source": "file:src/services/application.service.ts", + "target": "function:src/services/application.service.ts:listApplications", + "type": "exports", + "direction": "forward", + "weight": 0.8 + }, + { + "source": "file:src/services/application.service.ts", + "target": "function:src/services/application.service.ts:createApplication", + "type": "exports", + "direction": "forward", + "weight": 0.8 + }, + { + "source": "file:src/services/application.service.ts", + "target": "function:src/services/application.service.ts:updateApplication", + "type": "exports", + "direction": "forward", + "weight": 0.8 + }, + { + "source": "file:src/services/application.service.ts", + "target": "function:src/services/application.service.ts:deleteApplication", + "type": "exports", + "direction": "forward", + "weight": 0.8 + }, + { + "source": "file:src/services/application.service.ts", + "target": "function:src/services/application.service.ts:restoreToVersion", + "type": "exports", + "direction": "forward", + "weight": 0.8 + }, + { + "source": "file:src/services/csv.service.ts", + "target": "function:src/services/csv.service.ts:parseCsvRows", + "type": "contains", + "direction": "forward", + "weight": 1.0 + }, + { + "source": "file:src/services/csv.service.ts", + "target": "function:src/services/csv.service.ts:importApplications", + "type": "contains", + "direction": "forward", + "weight": 1.0 + }, + { + "source": "file:src/services/csv.service.ts", + "target": "function:src/services/csv.service.ts:exportApplications", + "type": "contains", + "direction": "forward", + "weight": 1.0 + }, + { + "source": "file:src/services/csv.service.ts", + "target": "function:src/services/csv.service.ts:parseCsvRows", + "type": "exports", + "direction": "forward", + "weight": 0.8 + }, + { + "source": "file:src/services/csv.service.ts", + "target": "function:src/services/csv.service.ts:importApplications", + "type": "exports", + "direction": "forward", + "weight": 0.8 + }, + { + "source": "file:src/services/csv.service.ts", + "target": "function:src/services/csv.service.ts:exportApplications", + "type": "exports", + "direction": "forward", + "weight": 0.8 + }, + { + "source": "file:src/services/history.service.ts", + "target": "function:src/services/history.service.ts:recordHistory", + "type": "contains", + "direction": "forward", + "weight": 1.0 + }, + { + "source": "file:src/services/history.service.ts", + "target": "function:src/services/history.service.ts:computeFieldDiffs", + "type": "contains", + "direction": "forward", + "weight": 1.0 + }, + { + "source": "file:src/services/history.service.ts", + "target": "function:src/services/history.service.ts:recordHistory", + "type": "exports", + "direction": "forward", + "weight": 0.8 + }, + { + "source": "file:src/services/history.service.ts", + "target": "function:src/services/history.service.ts:computeFieldDiffs", + "type": "exports", + "direction": "forward", + "weight": 0.8 + }, + { + "source": "file:src/services/interview-stage.service.ts", + "target": "function:src/services/interview-stage.service.ts:createInterviewStage", + "type": "contains", + "direction": "forward", + "weight": 1.0 + }, + { + "source": "file:src/services/interview-stage.service.ts", + "target": "function:src/services/interview-stage.service.ts:deleteInterviewStage", + "type": "contains", + "direction": "forward", + "weight": 1.0 + }, + { + "source": "file:src/services/interview-stage.service.ts", + "target": "function:src/services/interview-stage.service.ts:createInterviewStage", + "type": "exports", + "direction": "forward", + "weight": 0.8 + }, + { + "source": "file:src/services/interview-stage.service.ts", + "target": "function:src/services/interview-stage.service.ts:deleteInterviewStage", + "type": "exports", + "direction": "forward", + "weight": 0.8 + }, + { + "source": "config:tsconfig.json", + "target": "file:src/app.ts", + "type": "configures", + "direction": "forward", + "weight": 0.6 + }, + { + "source": "config:tsconfig.json", + "target": "file:src/handler.ts", + "type": "configures", + "direction": "forward", + "weight": 0.6 + }, + { + "source": "config:cdk/tsconfig.json", + "target": "file:cdk/lib/lambda-api-stack.ts", + "type": "configures", + "direction": "forward", + "weight": 0.6 + }, + { + "source": "config:.env.example", + "target": "file:src/services/dynamodb.client.ts", + "type": "configures", + "direction": "forward", + "weight": 0.6 + }, + { + "source": "config:.env.example", + "target": "file:src/server.ts", + "type": "configures", + "direction": "forward", + "weight": 0.6 + }, + { + "source": "file:cdk/lib/lambda-api-stack.ts", + "target": "file:src/handler.ts", + "type": "deploys", + "direction": "forward", + "weight": 0.7 + }, + { + "source": "document:CLAUDE.md", + "target": "file:src/services/dynamodb.client.ts", + "type": "documents", + "direction": "forward", + "weight": 0.5 + }, + { + "source": "document:CLAUDE.md", + "target": "file:src/handler.ts", + "type": "documents", + "direction": "forward", + "weight": 0.5 + }, + { + "source": "document:CLAUDE.md", + "target": "file:cdk/lib/lambda-api-stack.ts", + "type": "documents", + "direction": "forward", + "weight": 0.5 + }, + { + "source": "file:src/__tests__/csv.test.ts", + "target": "file:src/services/csv.service.ts", + "type": "tested_by", + "direction": "forward", + "weight": 0.5 + }, + { + "source": "file:src/__tests__/unit.test.ts", + "target": "file:src/services/application.service.ts", + "type": "tested_by", + "direction": "forward", + "weight": 0.5 + }, + { + "source": "file:src/app.ts", + "target": "file:src/routes/applications.ts", + "type": "depends_on", + "direction": "forward", + "weight": 0.6 + }, + { + "source": "file:src/app.ts", + "target": "file:src/routes/health.ts", + "type": "depends_on", + "direction": "forward", + "weight": 0.6 + }, + { + "source": "file:src/handler.ts", + "target": "file:src/app.ts", + "type": "depends_on", + "direction": "forward", + "weight": 0.6 + }, + { + "source": "file:src/server.ts", + "target": "file:src/app.ts", + "type": "depends_on", + "direction": "forward", + "weight": 0.6 + }, + { + "source": "file:src/routes/applications.ts", + "target": "file:src/services/application.service.ts", + "type": "depends_on", + "direction": "forward", + "weight": 0.6 + }, + { + "source": "file:src/routes/applications.ts", + "target": "file:src/services/csv.service.ts", + "type": "depends_on", + "direction": "forward", + "weight": 0.6 + }, + { + "source": "file:src/routes/applications.ts", + "target": "file:src/services/history.service.ts", + "type": "depends_on", + "direction": "forward", + "weight": 0.6 + }, + { + "source": "file:src/routes/applications.ts", + "target": "file:src/services/interview-stage.service.ts", + "type": "depends_on", + "direction": "forward", + "weight": 0.6 + }, + { + "source": "file:src/routes/applications.ts", + "target": "file:src/types/api.ts", + "type": "depends_on", + "direction": "forward", + "weight": 0.6 + }, + { + "source": "file:src/services/application.service.ts", + "target": "file:src/services/dynamodb.client.ts", + "type": "depends_on", + "direction": "forward", + "weight": 0.6 + }, + { + "source": "file:src/services/application.service.ts", + "target": "file:src/types/dynamo.ts", + "type": "depends_on", + "direction": "forward", + "weight": 0.6 + }, + { + "source": "file:src/services/application.service.ts", + "target": "file:src/types/api.ts", + "type": "depends_on", + "direction": "forward", + "weight": 0.6 + }, + { + "source": "file:src/services/application.service.ts", + "target": "file:src/services/history.service.ts", + "type": "depends_on", + "direction": "forward", + "weight": 0.6 + }, + { + "source": "file:src/services/csv.service.ts", + "target": "file:src/services/application.service.ts", + "type": "depends_on", + "direction": "forward", + "weight": 0.6 + }, + { + "source": "file:src/services/csv.service.ts", + "target": "file:src/services/dynamodb.client.ts", + "type": "depends_on", + "direction": "forward", + "weight": 0.6 + }, + { + "source": "file:src/services/csv.service.ts", + "target": "file:src/types/api.ts", + "type": "depends_on", + "direction": "forward", + "weight": 0.6 + }, + { + "source": "file:src/services/history.service.ts", + "target": "file:src/services/dynamodb.client.ts", + "type": "depends_on", + "direction": "forward", + "weight": 0.6 + }, + { + "source": "file:src/services/history.service.ts", + "target": "file:src/types/dynamo.ts", + "type": "depends_on", + "direction": "forward", + "weight": 0.6 + }, + { + "source": "file:src/services/interview-stage.service.ts", + "target": "file:src/services/dynamodb.client.ts", + "type": "depends_on", + "direction": "forward", + "weight": 0.6 + }, + { + "source": "file:src/services/interview-stage.service.ts", + "target": "file:src/types/dynamo.ts", + "type": "depends_on", + "direction": "forward", + "weight": 0.6 + }, + { + "source": "file:src/services/interview-stage.service.ts", + "target": "file:src/services/application.service.ts", + "type": "depends_on", + "direction": "forward", + "weight": 0.6 + }, + { + "source": "file:src/services/interview-stage.service.ts", + "target": "file:src/services/history.service.ts", + "type": "depends_on", + "direction": "forward", + "weight": 0.6 + }, + { + "source": "file:scripts/setup-dynamodb.ts", + "target": "file:src/services/dynamodb.client.ts", + "type": "related", + "direction": "forward", + "weight": 0.5 + } + ], + "layers": [ + { + "id": "layer:api", + "name": "API Layer", + "description": "Hono application factory, Lambda and local server entry points, and REST route handlers for job application CRUD and health check endpoints.", + "nodeIds": [ + "file:src/app.ts", + "file:src/handler.ts", + "file:src/server.ts", + "file:src/routes/applications.ts", + "file:src/routes/health.ts" + ] + }, + { + "id": "layer:service", + "name": "Service Layer", + "description": "Core DynamoDB CRUD services for applications, interview stages, history tracking, CSV import/export, and the singleton DynamoDB DocumentClient.", + "nodeIds": [ + "file:src/services/application.service.ts", + "file:src/services/csv.service.ts", + "file:src/services/dynamodb.client.ts", + "file:src/services/history.service.ts", + "file:src/services/interview-stage.service.ts" + ] + }, + { + "id": "layer:types", + "name": "Types Layer", + "description": "Zod schema definitions for API request/response shapes and DynamoDB single-table type definitions with key builder functions for APP#, STAGE#, and HIST# items.", + "nodeIds": [ + "file:src/types/api.ts", + "file:src/types/dynamo.ts" + ] + }, + { + "id": "layer:test", + "name": "Test Layer", + "description": "Vitest unit tests for the CSV service parser and application service CRUD functions.", + "nodeIds": [ + "file:src/__tests__/csv.test.ts", + "file:src/__tests__/unit.test.ts" + ] + }, + { + "id": "layer:infrastructure", + "name": "Infrastructure Layer", + "description": "AWS CDK sub-project defining the DynamoDB table, Lambda function, and API Gateway v2 stack, plus the idempotent DynamoDB Local table setup script.", + "nodeIds": [ + "file:cdk/bin/app.ts", + "file:cdk/lib/lambda-api-stack.ts", + "file:cdk/test/lambda-api-stack.test.ts", + "file:cdk/vitest.config.ts", + "config:cdk/.auditconfig.json", + "config:cdk/cdk.json", + "config:cdk/package.json", + "config:cdk/tsconfig.json", + "file:scripts/setup-dynamodb.ts" + ] + }, + { + "id": "layer:config", + "name": "Configuration", + "description": "Root-level build, lint, and test configuration files for the lambda-api package including TypeScript, Vitest, ESLint, audit-ci, and environment variable templates.", + "nodeIds": [ + "config:.auditconfig.json", + "config:.env.example", + "config:package.json", + "config:tsconfig.json", + "file:eslint.config.js", + "file:vitest.config.ts" + ] + }, + { + "id": "layer:documentation", + "name": "Documentation", + "description": "Stack-specific developer guidance covering DynamoDB single-table design patterns, local development workflow, and CDK deployment conventions.", + "nodeIds": [ + "document:CLAUDE.md" + ] + } + ], + "tour": [ + { + "order": 1, + "title": "Developer Guide", + "description": "CLAUDE.md is the first stop for any new contributor: it documents the DynamoDB single-table design (APP#/STAGE#/HIST# key prefixes, two GSIs, pagination contract), local dev patterns (DynamoDB Local on port 8000, dotenv init-order gotcha), and CDK deployment notes. Reading it before touching any code gives you the mental model that every other file assumes.", + "nodeIds": [ + "document:CLAUDE.md" + ] + }, + { + "order": 2, + "title": "Project Manifest", + "description": "The root package.json is the project's command centre, declaring the Hono, DynamoDB SDK, and Zod runtime dependencies alongside the build, dev, test, and migration scripts. It reveals the dual-runtime strategy at a glance: `dev` starts the local Node.js server while `migrate:lambda-api` initialises the DynamoDB Local table, with no mention of a PostgreSQL connection string anywhere.", + "nodeIds": [ + "config:package.json" + ], + "languageLesson": "In an ESM-first project (\"type\": \"module\"), all .js files are treated as ES modules. CDK's ts-node runner requires CommonJS, which is why the cdk/ sub-package has its own tsconfig.json with \"module\": \"CommonJS\" \u2014 a deliberate exception to the parent configuration." + }, + { + "order": 3, + "title": "Environment Configuration", + "description": "`.env.example` is the authoritative list of every runtime knob the application supports: the DynamoDB endpoint URL (pointing to localhost:8000 in development or absent in Lambda for real AWS), table name, AWS credentials, and server port. Two service files depend on it directly \u2014 `dynamodb.client.ts` reads the endpoint at module load time, and `server.ts` reads the port \u2014 which is why dotenv must be imported before any service module is required.", + "nodeIds": [ + "config:.env.example" + ] + }, + { + "order": 4, + "title": "Dual Runtime Entry Points", + "description": "The project has two entry points for the same Hono application. `server.ts` boots a plain Node.js HTTP server via `@hono/node-server` for local development on port 5090. `handler.ts` exports the AWS Lambda handler using `hono/aws-lambda`, which adapts the same Hono app to the Lambda event/context interface. The key insight is that neither file contains any business logic \u2014 they are thin wrappers around the shared `app.ts` factory.", + "nodeIds": [ + "file:src/server.ts", + "file:src/handler.ts" + ], + "languageLesson": "Hono's `handle()` adapter from `hono/aws-lambda` is bundled inside the main `hono` package \u2014 there is no separate npm package to install. The adapter translates the AWS APIGatewayProxyEventV2 shape into a standard Fetch API Request object that the Hono router understands." + }, + { + "order": 5, + "title": "Hono Application Factory", + "description": "With fanIn=3, `src/app.ts` is the shared core consumed by both `server.ts` and `handler.ts`. It is a factory function that wires CORS middleware, mounts the two routers (`/health` and the main applications routes), and registers the global error handler. Keeping it as a factory (rather than a singleton module) makes it straightforward to instantiate in tests without side effects.", + "nodeIds": [ + "file:src/app.ts", + "file:src/routes/health.ts" + ], + "languageLesson": "Hono uses a chainable router API similar to Express. Middleware is registered with `app.use()`, sub-routers are mounted with `app.route()`, and the factory returns the `app` instance. Unlike Express, Hono routes are typed \u2014 the context `c` object carries the validated request body and inferred response type at the TypeScript level." + }, + { + "order": 6, + "title": "Type Contract", + "description": "Before reading any service or route code it is worth understanding the type vocabulary they share. `src/types/api.ts` defines Zod schemas for every API request body and response shape \u2014 including the two pagination modes (offset-based and cursor-based) and the CSV import result envelope. `src/types/dynamo.ts` defines the DynamoDB item shapes, key builder functions (`appKey`, `stageKey`, `histKey`), and type guards that let services distinguish APP# from STAGE# from HIST# items at runtime. Both files are imported by three or more modules each (fanIn=3), making them the vocabulary of the entire codebase.", + "nodeIds": [ + "file:src/types/api.ts", + "file:src/types/dynamo.ts" + ], + "languageLesson": "Zod schemas serve a dual purpose here: they are both the TypeScript type source (via `z.infer`) and the runtime validator. This single-source-of-truth pattern means the shape of a request body is defined exactly once and is automatically enforced at the route layer before any service code runs." + }, + { + "order": 7, + "title": "DynamoDB Singleton Client", + "description": "`dynamodb.client.ts` is the single most depended-upon file in the project (fanIn=7) \u2014 every service module imports the DocumentClient from here. It creates the client exactly once at module load time, reading `DYNAMODB_ENDPOINT` from the environment: when set it points to DynamoDB Local for development; when absent the AWS SDK defaults to real AWS, which is the correct behaviour inside a Lambda function. This module-level singleton pattern avoids creating a new TCP connection on every invocation inside Lambda.", + "nodeIds": [ + "file:src/services/dynamodb.client.ts" + ], + "languageLesson": "Module-level singletons in Node.js ESM are safe because the module cache ensures the file is evaluated only once per process. In Lambda, the execution environment is reused across warm invocations, so a singleton client avoids connection overhead on every request \u2014 a meaningful latency optimisation for DynamoDB." + }, + { + "order": 8, + "title": "Applications Route Handler", + "description": "`src/routes/applications.ts` is the broadest file in the codebase by fan-out (fanOut=5), connecting the HTTP layer to every service and both type modules. Each route handler parses and validates the request with a Zod schema from `api.ts`, then delegates to the appropriate service. This file is the best place to map the API surface \u2014 GET/POST/PUT/DELETE for applications, PATCH for status, and dedicated sub-routes for CSV import/export, history, and interview stages.", + "nodeIds": [ + "file:src/routes/applications.ts" + ], + "languageLesson": "Hono's `zValidator` middleware integrates Zod schemas directly into the route definition. When validation fails, Hono returns a 400 response automatically before the handler runs, so service functions never receive malformed input. The validated data is available via `c.req.valid('json')` with full TypeScript inference." + }, + { + "order": 9, + "title": "Core Application Service", + "description": "With fanIn=4, `application.service.ts` is the second most depended-upon module in the project. It implements all CRUD operations for job application items (APP# keys), including paginated list queries against both GSIs, soft-archive logic, version restore from HIST# snapshots, and the `touchApplication` helper that updates parent timestamps when child items change. The history service is called here on every write, wiring in audit tracking transparently from the consumer's perspective.", + "nodeIds": [ + "file:src/services/application.service.ts" + ], + "languageLesson": "DynamoDB's `UpdateCommand` supports atomic counter increments using the `ADD` action (`ADD historySequence :inc`). The application service uses this to safely increment the history sequence number \u2014 even under concurrent Lambda invocations \u2014 without needing a read-then-write cycle that could produce duplicate sequence numbers." + }, + { + "order": 10, + "title": "History & Stage Services", + "description": "Two specialised services handle the sub-items that live alongside each application in the single table. `history.service.ts` writes HIST# items by reading the atomic sequence counter, computing field-level diffs between the old and new application state, and persisting a snapshot. `interview-stage.service.ts` handles CRUD for STAGE# items and calls `touchApplication` after every write so the parent application's `updatedAt` timestamp reflects stage changes. Together these two services (fanIn=3 each) implement the audit trail and interview tracking features visible in the application UI.", + "nodeIds": [ + "file:src/services/history.service.ts", + "file:src/services/interview-stage.service.ts" + ] + }, + { + "order": 11, + "title": "CSV Import/Export Service", + "description": "`csv.service.ts` implements bulk data portability with a character-by-character CSV parser (required to handle quoted fields containing embedded newlines), Zod-validated row mapping, and deduplication logic that prevents duplicate applications on re-import. It depends on `application.service.ts` for writing records, making CSV import a thin orchestration layer rather than a separate data path. The accompanying `csv.test.ts` exercises the parser extensively because the multi-line field edge cases are where bugs hide.", + "nodeIds": [ + "file:src/services/csv.service.ts", + "file:src/__tests__/csv.test.ts" + ], + "languageLesson": "Multi-line CSV fields require character-by-character state machine parsing \u2014 you cannot split the file on newlines first because quoted fields can legitimately contain `\\n`. The parser tracks an `inQuotes` boolean and only treats `\\n` as a row boundary when that flag is false." + }, + { + "order": 12, + "title": "Unit Test Suite", + "description": "`src/__tests__/unit.test.ts` tests the application service functions as pure units without a running DynamoDB instance. It covers CRUD operations, pagination boundary conditions, and input validation, giving contributors a fast feedback loop that does not require Docker. These tests deliberately do not mock the service internals \u2014 they call the exported functions directly and assert on return values, keeping the tests decoupled from implementation details.", + "nodeIds": [ + "file:src/__tests__/unit.test.ts", + "file:vitest.config.ts" + ] + }, + { + "order": 13, + "title": "DynamoDB Table Setup", + "description": "`scripts/setup-dynamodb.ts` is the migration script run by `npm run migrate:lambda-api`. It creates the `lambda_api_applications` table with the two GSIs (GSI1 for status+archived filtering, GSI2 for all active applications sorted by updatedAt) and exits without error if the table already exists. Reading this script alongside `dynamo.ts` gives you the complete picture of the single-table schema: the GSI key patterns defined here are the indexes that the application service queries.", + "nodeIds": [ + "file:scripts/setup-dynamodb.ts" + ] + }, + { + "order": 14, + "title": "CDK Infrastructure Stack", + "description": "The `cdk/` sub-project is a self-contained AWS CDK application that provisions everything the Lambda needs in AWS: a DynamoDB `TableV2` with the same two GSIs defined in `setup-dynamodb.ts`, a `NodejsFunction` that bundles `src/handler.ts` via esbuild, and an HTTP API Gateway v2 that routes all traffic to the Lambda. `cdk/bin/app.ts` is the CDK application entry point that instantiates `LambdaApiStack`, and `cdk/test/lambda-api-stack.test.ts` asserts the synthesized CloudFormation template matches the expected resource shapes.", + "nodeIds": [ + "file:cdk/bin/app.ts", + "file:cdk/lib/lambda-api-stack.ts", + "file:cdk/test/lambda-api-stack.test.ts" + ], + "languageLesson": "CDK's `NodejsFunction` uses esbuild to bundle TypeScript at `cdk synth` time, producing a single-file Lambda deployment package. The bundle entry point is the `handler.ts` file from Step 4, which is how the CDK stack and the application code are connected \u2014 the CDK stack does not import the application code at TypeScript level, it references it as a file path string passed to `NodejsFunction`." + } + ] +} \ No newline at end of file diff --git a/lambda-api/.understand-anything/meta.json b/lambda-api/.understand-anything/meta.json new file mode 100644 index 00000000..afd8c45d --- /dev/null +++ b/lambda-api/.understand-anything/meta.json @@ -0,0 +1,6 @@ +{ + "lastAnalyzedAt": "2026-05-04T14:36:51.980392+00:00", + "gitCommitHash": "e5054096efec8165e7d4cfe11fc103f77ad2d98f", + "version": "1.0.0", + "analyzedFiles": 30 +} \ No newline at end of file diff --git a/lambda-react-ui/.understand-anything/.understandignore b/lambda-react-ui/.understand-anything/.understandignore new file mode 100644 index 00000000..bcc9613d --- /dev/null +++ b/lambda-react-ui/.understand-anything/.understandignore @@ -0,0 +1,13 @@ +# .understandignore — patterns for files/dirs to exclude from analysis +# Syntax: same as .gitignore (globs, # comments, ! negation, trailing / for dirs) + +# Built-in defaults (always excluded unless negated): +# node_modules/, .git/, dist/, build/, *.lock, *.min.js, etc. + +# Exclude test results and assets +test-results/ +src/assets/ + +# Exclude test files (comment out to include) +# *.test.* +# *.spec.* diff --git a/lambda-react-ui/.understand-anything/knowledge-graph.json b/lambda-react-ui/.understand-anything/knowledge-graph.json new file mode 100644 index 00000000..ca728d7f --- /dev/null +++ b/lambda-react-ui/.understand-anything/knowledge-graph.json @@ -0,0 +1,2390 @@ +{ + "version": "1.0.0", + "project": { + "name": "lambda-react-ui", + "languages": [ + "css", + "html", + "javascript", + "json", + "markdown", + "typescript" + ], + "frameworks": [ + "React", + "React Router", + "Tailwind CSS", + "Vite", + "Vitest", + "Zustand" + ], + "description": "React 19 + Vite 7 + Zustand + Tailwind CSS 4 SPA frontend for the application tracker, paired with the lambda-api backend (Hono + DynamoDB).", + "analyzedAt": "2026-05-04T14:21:26.335301Z", + "gitCommitHash": "e5054096efec8165e7d4cfe11fc103f77ad2d98f" + }, + "nodes": [ + { + "id": "config:.auditconfig.json", + "type": "config", + "name": ".auditconfig.json", + "filePath": ".auditconfig.json", + "summary": "audit-ci configuration allowing specific high-severity advisories by GHSA ID and setting the package manager to npm.", + "tags": [ + "configuration", + "security", + "build-system" + ], + "complexity": "simple" + }, + { + "id": "file:eslint.config.js", + "type": "file", + "name": "eslint.config.js", + "filePath": "eslint.config.js", + "summary": "ESLint flat-config file enabling TypeScript and React hooks rules for the Vite-based React project.", + "tags": [ + "configuration", + "linting", + "build-system" + ], + "complexity": "simple" + }, + { + "id": "file:index.html", + "type": "file", + "name": "index.html", + "filePath": "index.html", + "summary": "Root HTML entry point for the Vite SPA, mounting the React app into a #root div and loading the TypeScript main entry.", + "tags": [ + "entry-point", + "markup", + "infrastructure" + ], + "complexity": "simple" + }, + { + "id": "config:package.json", + "type": "config", + "name": "package.json", + "filePath": "package.json", + "summary": "Package manifest for the lambda-react-ui frontend defining scripts, React 19 and Tailwind CSS 4 dependencies, and Vite 7 dev tooling.", + "tags": [ + "configuration", + "build-system", + "dependency-management" + ], + "complexity": "simple" + }, + { + "id": "file:postcss.config.js", + "type": "file", + "name": "postcss.config.js", + "filePath": "postcss.config.js", + "summary": "PostCSS configuration enabling the Tailwind CSS plugin for CSS processing during the Vite build.", + "tags": [ + "configuration", + "build-system", + "css" + ], + "complexity": "simple" + }, + { + "id": "document:README.md", + "type": "document", + "name": "README.md", + "filePath": "README.md", + "summary": "Developer guide covering ports, prerequisites, development server, testing, and build instructions for the lambda-react-ui SPA.", + "tags": [ + "documentation", + "entry-point", + "development" + ], + "complexity": "simple" + }, + { + "id": "file:src/App.tsx", + "type": "file", + "name": "App.tsx", + "filePath": "src/App.tsx", + "summary": "Root React component that sets up React Router routes and conditionally renders the ContextPanel based on Zustand UI store state.", + "tags": [ + "entry-point", + "component", + "routing" + ], + "complexity": "simple" + }, + { + "id": "function:src/App.tsx:App", + "type": "function", + "name": "App", + "filePath": "src/App.tsx", + "lineRange": [ + 7, + 24 + ], + "summary": "Root application component that reads UI state from useUiStore and renders the router layout including the optional ContextPanel.", + "tags": [ + "component", + "entry-point", + "routing" + ], + "complexity": "simple" + }, + { + "id": "file:src/components/applications/ApplicationCard.test.tsx", + "type": "file", + "name": "ApplicationCard.test.tsx", + "filePath": "src/components/applications/ApplicationCard.test.tsx", + "summary": "Unit tests for ApplicationCard verifying rendering of application data, overdue badge display, and salary range formatting.", + "tags": [ + "test", + "component", + "validation" + ], + "complexity": "moderate" + }, + { + "id": "file:src/components/applications/ApplicationCard.tsx", + "type": "file", + "name": "ApplicationCard.tsx", + "filePath": "src/components/applications/ApplicationCard.tsx", + "summary": "Card component rendering a single job application with status badge, salary, deadline overdue indicators, and a context menu toggle.", + "tags": [ + "component", + "ui", + "data-model" + ], + "complexity": "moderate" + }, + { + "id": "function:src/components/applications/ApplicationCard.tsx:ApplicationCard", + "type": "function", + "name": "ApplicationCard", + "filePath": "src/components/applications/ApplicationCard.tsx", + "lineRange": [ + 15, + 99 + ], + "summary": "Exported card component displaying company/position initials avatar, status badge, formatted date, salary, and overdue countdown for a job application.", + "tags": [ + "component", + "ui", + "event-handler" + ], + "complexity": "moderate" + }, + { + "id": "file:src/components/applications/ApplicationForm.test.tsx", + "type": "file", + "name": "ApplicationForm.test.tsx", + "filePath": "src/components/applications/ApplicationForm.test.tsx", + "summary": "Unit tests for ApplicationForm verifying field rendering, validation error messages, and dirty-state callback behavior.", + "tags": [ + "test", + "component", + "validation" + ], + "complexity": "moderate" + }, + { + "id": "file:src/components/applications/ApplicationForm.tsx", + "type": "file", + "name": "ApplicationForm.tsx", + "filePath": "src/components/applications/ApplicationForm.tsx", + "summary": "Large form component for creating and editing job applications with inline validation, status transitions, salary fields, URL normalization, and an embedded interview stage draft editor.", + "tags": [ + "component", + "form", + "validation" + ], + "complexity": "complex" + }, + { + "id": "function:src/components/applications/ApplicationForm.tsx:ApplicationForm", + "type": "function", + "name": "ApplicationForm", + "filePath": "src/components/applications/ApplicationForm.tsx", + "lineRange": [ + 74, + 427 + ], + "summary": "Exported form component managing create/edit modes for job applications with controlled field state, inline validation, status-driven date auto-fill, and draft interview stage list.", + "tags": [ + "component", + "form", + "validation" + ], + "complexity": "complex" + }, + { + "id": "function:src/components/applications/ApplicationForm.tsx:normalizeInitialValues", + "type": "function", + "name": "normalizeInitialValues", + "filePath": "src/components/applications/ApplicationForm.tsx", + "lineRange": [ + 48, + 67 + ], + "summary": "Converts raw application values to string-safe form field defaults, coercing numeric salary fields to empty strings when absent.", + "tags": [ + "utility", + "form", + "serialization" + ], + "complexity": "simple" + }, + { + "id": "file:src/components/applications/ApplicationList.tsx", + "type": "file", + "name": "ApplicationList.tsx", + "filePath": "src/components/applications/ApplicationList.tsx", + "summary": "Paginated list component rendering ApplicationCard items in grid or list view with per-card context menus and archive/restore/delete confirmation dialogs.", + "tags": [ + "component", + "ui", + "pagination" + ], + "complexity": "complex" + }, + { + "id": "function:src/components/applications/ApplicationList.tsx:ApplicationList", + "type": "function", + "name": "ApplicationList", + "filePath": "src/components/applications/ApplicationList.tsx", + "lineRange": [ + 24, + 200 + ], + "summary": "Exported list component managing paginated application cards with open menu state, skeleton loaders, confirm dialogs for destructive actions, and page navigation.", + "tags": [ + "component", + "ui", + "pagination" + ], + "complexity": "complex" + }, + { + "id": "file:src/components/applications/CsvImportModal.tsx", + "type": "file", + "name": "CsvImportModal.tsx", + "filePath": "src/components/applications/CsvImportModal.tsx", + "summary": "Modal component allowing users to upload a CSV file for bulk application import, with error reporting, import result summary, and sample CSV download.", + "tags": [ + "component", + "modal", + "data-pipeline" + ], + "complexity": "moderate" + }, + { + "id": "function:src/components/applications/CsvImportModal.tsx:CsvImportModal", + "type": "function", + "name": "CsvImportModal", + "filePath": "src/components/applications/CsvImportModal.tsx", + "lineRange": [ + 13, + 104 + ], + "summary": "Exported modal component that manages file selection, triggers the importCSV API call, displays per-row errors, and provides a sample CSV download link.", + "tags": [ + "component", + "modal", + "data-pipeline" + ], + "complexity": "moderate" + }, + { + "id": "file:src/components/applications/FieldDiff.tsx", + "type": "file", + "name": "FieldDiff.tsx", + "filePath": "src/components/applications/FieldDiff.tsx", + "summary": "Small presentational component that renders a labeled before/after diff row for a single field in the history panel.", + "tags": [ + "component", + "ui", + "utility" + ], + "complexity": "simple" + }, + { + "id": "function:src/components/applications/FieldDiff.tsx:FieldDiff", + "type": "function", + "name": "FieldDiff", + "filePath": "src/components/applications/FieldDiff.tsx", + "lineRange": [ + 20, + 29 + ], + "summary": "Exported component rendering a label and two colored spans showing the old and new values of a changed field.", + "tags": [ + "component", + "ui", + "utility" + ], + "complexity": "simple" + }, + { + "id": "file:src/components/applications/FilterBar.test.tsx", + "type": "file", + "name": "FilterBar.test.tsx", + "filePath": "src/components/applications/FilterBar.test.tsx", + "summary": "Unit tests for FilterBar verifying filter toggle behavior and Zustand store state resets between test cases.", + "tags": [ + "test", + "component", + "validation" + ], + "complexity": "simple" + }, + { + "id": "file:src/components/applications/FilterBar.tsx", + "type": "file", + "name": "FilterBar.tsx", + "filePath": "src/components/applications/FilterBar.tsx", + "summary": "Toolbar component for filtering and sorting the application list by status, category, source, and skills match, with CSV export/import actions and view-mode toggle.", + "tags": [ + "component", + "filter", + "ui" + ], + "complexity": "complex" + }, + { + "id": "function:src/components/applications/FilterBar.tsx:FilterBar", + "type": "function", + "name": "FilterBar", + "filePath": "src/components/applications/FilterBar.tsx", + "lineRange": [ + 19, + 217 + ], + "summary": "Exported toolbar component reading and writing filter/sort state from Zustand stores, rendering status checkboxes, dropdowns, sort controls, and CSV import/export buttons.", + "tags": [ + "component", + "filter", + "event-handler" + ], + "complexity": "complex" + }, + { + "id": "file:src/components/applications/HistoryPanel.tsx", + "type": "file", + "name": "HistoryPanel.tsx", + "filePath": "src/components/applications/HistoryPanel.tsx", + "summary": "Panel component that loads and displays paginated revision history for an application, with expandable field diffs and version restore capability.", + "tags": [ + "component", + "ui", + "history" + ], + "complexity": "complex" + }, + { + "id": "function:src/components/applications/HistoryPanel.tsx:HistoryPanel", + "type": "function", + "name": "HistoryPanel", + "filePath": "src/components/applications/HistoryPanel.tsx", + "lineRange": [ + 25, + 151 + ], + "summary": "Exported panel component fetching paginated history entries from the store, rendering relative timestamps, expandable FieldDiff rows, and a restore-to-version action.", + "tags": [ + "component", + "ui", + "history" + ], + "complexity": "complex" + }, + { + "id": "file:src/components/interviews/InterviewStageForm.tsx", + "type": "file", + "name": "InterviewStageForm.tsx", + "filePath": "src/components/interviews/InterviewStageForm.tsx", + "summary": "Form component for creating or editing an interview stage with name, type, scheduled/completed date fields, and delete capability.", + "tags": [ + "component", + "form", + "validation" + ], + "complexity": "complex" + }, + { + "id": "function:src/components/interviews/InterviewStageForm.tsx:InterviewStageForm", + "type": "function", + "name": "InterviewStageForm", + "filePath": "src/components/interviews/InterviewStageForm.tsx", + "lineRange": [ + 19, + 146 + ], + "summary": "Exported form component managing interview stage create/edit/delete with completed-toggle auto-dating, inline validation, and saving/canceling callbacks.", + "tags": [ + "component", + "form", + "validation" + ], + "complexity": "complex" + }, + { + "id": "file:src/components/interviews/InterviewStageItem.tsx", + "type": "file", + "name": "InterviewStageItem.tsx", + "filePath": "src/components/interviews/InterviewStageItem.tsx", + "summary": "List item component for a single interview stage showing completion status, inline edit toggle, and delete confirmation dialog.", + "tags": [ + "component", + "ui", + "event-handler" + ], + "complexity": "moderate" + }, + { + "id": "function:src/components/interviews/InterviewStageItem.tsx:InterviewStageItem", + "type": "function", + "name": "InterviewStageItem", + "filePath": "src/components/interviews/InterviewStageItem.tsx", + "lineRange": [ + 17, + 113 + ], + "summary": "Exported list item toggling between read and inline-edit modes, handling completion checkbox, update submission, and delete confirmation with error display.", + "tags": [ + "component", + "ui", + "event-handler" + ], + "complexity": "moderate" + }, + { + "id": "file:src/components/interviews/InterviewStageList.tsx", + "type": "file", + "name": "InterviewStageList.tsx", + "filePath": "src/components/interviews/InterviewStageList.tsx", + "summary": "Container component rendering the ordered list of interview stages with add-new form, progress bar, and default stage seeding.", + "tags": [ + "component", + "ui", + "list" + ], + "complexity": "moderate" + }, + { + "id": "function:src/components/interviews/InterviewStageList.tsx:InterviewStageList", + "type": "function", + "name": "InterviewStageList", + "filePath": "src/components/interviews/InterviewStageList.tsx", + "lineRange": [ + 13, + 92 + ], + "summary": "Exported component sorting stages by order, computing completion progress, wiring store actions for add/update/remove, and rendering InterviewStageItem plus an optional creation form.", + "tags": [ + "component", + "ui", + "list" + ], + "complexity": "moderate" + }, + { + "id": "file:src/components/layout/ContextPanel.tsx", + "type": "file", + "name": "ContextPanel.tsx", + "filePath": "src/components/layout/ContextPanel.tsx", + "summary": "Right-side detail panel orchestrating application view, edit, and history tabs with inline field editing, archive/restore/delete actions, and navigation to the edit form.", + "tags": [ + "component", + "layout", + "event-handler" + ], + "complexity": "complex" + }, + { + "id": "function:src/components/layout/ContextPanel.tsx:ContextPanel", + "type": "function", + "name": "ContextPanel", + "filePath": "src/components/layout/ContextPanel.tsx", + "lineRange": [ + 11, + 293 + ], + "summary": "Exported panel component reading selected application state from multiple Zustand stores, managing inline edits with on-blur persistence, tab switching, and confirm dialogs for destructive operations.", + "tags": [ + "component", + "layout", + "event-handler" + ], + "complexity": "complex" + }, + { + "id": "file:src/components/layout/PipelineSummaryBar.tsx", + "type": "file", + "name": "PipelineSummaryBar.tsx", + "filePath": "src/components/layout/PipelineSummaryBar.tsx", + "summary": "Visual pipeline bar component showing segmented counts of applications by status group with click-to-filter behavior.", + "tags": [ + "component", + "ui", + "filter" + ], + "complexity": "simple" + }, + { + "id": "function:src/components/layout/PipelineSummaryBar.tsx:PipelineSummaryBar", + "type": "function", + "name": "PipelineSummaryBar", + "filePath": "src/components/layout/PipelineSummaryBar.tsx", + "lineRange": [ + 27, + 70 + ], + "summary": "Exported bar component computing per-segment application counts from a passed array, highlighting the active segment, and dispatching status filter updates to Zustand on click.", + "tags": [ + "component", + "ui", + "filter" + ], + "complexity": "simple" + }, + { + "id": "file:src/components/layout/Sidebar.tsx", + "type": "file", + "name": "Sidebar.tsx", + "filePath": "src/components/layout/Sidebar.tsx", + "summary": "Navigation sidebar component with view links (All, Active, Archived, Interviews, Analytics, Settings) that update the filter store and navigate using React Router.", + "tags": [ + "component", + "layout", + "navigation" + ], + "complexity": "moderate" + }, + { + "id": "function:src/components/layout/Sidebar.tsx:Sidebar", + "type": "function", + "name": "Sidebar", + "filePath": "src/components/layout/Sidebar.tsx", + "lineRange": [ + 6, + 133 + ], + "summary": "Exported sidebar component reading UI and filter store state to highlight the active nav item, navigating routes, and toggling archived/status filters.", + "tags": [ + "component", + "layout", + "navigation" + ], + "complexity": "moderate" + }, + { + "id": "file:src/components/ui/Badge.tsx", + "type": "file", + "name": "Badge.tsx", + "filePath": "src/components/ui/Badge.tsx", + "summary": "Reusable status badge component mapping ApplicationStatus enum values to colored pill labels.", + "tags": [ + "component", + "ui", + "utility" + ], + "complexity": "simple" + }, + { + "id": "function:src/components/ui/Badge.tsx:Badge", + "type": "function", + "name": "Badge", + "filePath": "src/components/ui/Badge.tsx", + "lineRange": [ + 8, + 23 + ], + "summary": "Exported badge component rendering a styled span with color and label derived from a STATUS_COLORS and STATUS_LABELS lookup.", + "tags": [ + "component", + "ui", + "utility" + ], + "complexity": "simple" + }, + { + "id": "file:src/components/ui/Button.tsx", + "type": "file", + "name": "Button.tsx", + "filePath": "src/components/ui/Button.tsx", + "summary": "Reusable button component supporting primary/secondary/danger/ghost variants, multiple sizes, and a loading spinner state.", + "tags": [ + "component", + "ui", + "utility" + ], + "complexity": "simple" + }, + { + "id": "function:src/components/ui/Button.tsx:Button", + "type": "function", + "name": "Button", + "filePath": "src/components/ui/Button.tsx", + "lineRange": [ + 29, + 53 + ], + "summary": "Exported button component composing variant and size class names via cn(), rendering a spinner when loading, and forwarding remaining HTML button props.", + "tags": [ + "component", + "ui", + "utility" + ], + "complexity": "simple" + }, + { + "id": "file:src/components/ui/ConfirmDialog.test.tsx", + "type": "file", + "name": "ConfirmDialog.test.tsx", + "filePath": "src/components/ui/ConfirmDialog.test.tsx", + "summary": "Unit tests for ConfirmDialog verifying open/closed rendering, confirm and cancel button callbacks, and custom label support.", + "tags": [ + "test", + "component", + "validation" + ], + "complexity": "simple" + }, + { + "id": "file:src/components/ui/ConfirmDialog.tsx", + "type": "file", + "name": "ConfirmDialog.tsx", + "filePath": "src/components/ui/ConfirmDialog.tsx", + "summary": "Modal confirmation dialog component with configurable title, message, confirm/cancel labels, and a danger variant for destructive actions.", + "tags": [ + "component", + "modal", + "ui" + ], + "complexity": "simple" + }, + { + "id": "function:src/components/ui/ConfirmDialog.tsx:ConfirmDialog", + "type": "function", + "name": "ConfirmDialog", + "filePath": "src/components/ui/ConfirmDialog.tsx", + "lineRange": [ + 15, + 47 + ], + "summary": "Exported dialog component wrapping Modal and two Button components to present a configurable confirmation prompt with danger-variant support.", + "tags": [ + "component", + "modal", + "ui" + ], + "complexity": "simple" + }, + { + "id": "file:src/components/ui/EmptyState.tsx", + "type": "file", + "name": "EmptyState.tsx", + "filePath": "src/components/ui/EmptyState.tsx", + "summary": "Minimal empty-state presentational component displaying an icon, title, description, and optional action element when a list has no items.", + "tags": [ + "component", + "ui", + "utility" + ], + "complexity": "simple" + }, + { + "id": "function:src/components/ui/EmptyState.tsx:EmptyState", + "type": "function", + "name": "EmptyState", + "filePath": "src/components/ui/EmptyState.tsx", + "lineRange": [ + 10, + 19 + ], + "summary": "Exported empty-state component rendering an icon, heading, description paragraph, and an optional action slot centered in the container.", + "tags": [ + "component", + "ui", + "utility" + ], + "complexity": "simple" + }, + { + "id": "file:src/components/ui/Input.tsx", + "type": "file", + "name": "Input.tsx", + "filePath": "src/components/ui/Input.tsx", + "summary": "Reusable accessible text input component with label, validation error display, and support for all standard HTML input props.", + "tags": [ + "component", + "ui", + "form", + "utility" + ], + "complexity": "simple" + }, + { + "id": "function:src/components/ui/Input.tsx:Input", + "type": "function", + "name": "Input", + "filePath": "src/components/ui/Input.tsx", + "lineRange": [ + 9, + 31 + ], + "summary": "Exported React component rendering a labeled input with optional error message and auto-generated id from the label.", + "tags": [ + "component", + "form", + "ui" + ], + "complexity": "simple" + }, + { + "id": "file:src/components/ui/Modal.tsx", + "type": "file", + "name": "Modal.tsx", + "filePath": "src/components/ui/Modal.tsx", + "summary": "Accessible modal dialog component that renders via a React portal, traps Escape key to close, and blocks scroll propagation.", + "tags": [ + "component", + "ui", + "modal", + "accessibility" + ], + "complexity": "moderate" + }, + { + "id": "function:src/components/ui/Modal.tsx:Modal", + "type": "function", + "name": "Modal", + "filePath": "src/components/ui/Modal.tsx", + "lineRange": [ + 10, + 51 + ], + "summary": "Portal-based modal that listens for Escape key events via useEffect and stops click propagation from its content area.", + "tags": [ + "component", + "modal", + "portal", + "event-handler" + ], + "complexity": "moderate" + }, + { + "id": "file:src/components/ui/Pagination.test.tsx", + "type": "file", + "name": "Pagination.test.tsx", + "filePath": "src/components/ui/Pagination.test.tsx", + "summary": "Unit tests for the Pagination component verifying rendering and navigation button behavior across page boundaries.", + "tags": [ + "test", + "component", + "ui", + "pagination" + ], + "complexity": "simple" + }, + { + "id": "file:src/components/ui/Pagination.tsx", + "type": "file", + "name": "Pagination.tsx", + "filePath": "src/components/ui/Pagination.tsx", + "summary": "Pagination bar component rendering numbered page buttons with Previous/Next controls, disabling navigation at boundaries.", + "tags": [ + "component", + "ui", + "pagination", + "navigation" + ], + "complexity": "moderate" + }, + { + "id": "function:src/components/ui/Pagination.tsx:Pagination", + "type": "function", + "name": "Pagination", + "filePath": "src/components/ui/Pagination.tsx", + "lineRange": [ + 9, + 56 + ], + "summary": "Exported pagination component that builds an array of page numbers and delegates page changes to an onPage callback.", + "tags": [ + "component", + "pagination", + "navigation" + ], + "complexity": "moderate" + }, + { + "id": "file:src/components/ui/Rating.tsx", + "type": "file", + "name": "Rating.tsx", + "filePath": "src/components/ui/Rating.tsx", + "summary": "Star rating UI with two exported components: RatingDisplay for read-only display and RatingInput for interactive selection of 1-5 stars.", + "tags": [ + "component", + "ui", + "rating", + "form" + ], + "complexity": "moderate" + }, + { + "id": "function:src/components/ui/Rating.tsx:RatingDisplay", + "type": "function", + "name": "RatingDisplay", + "filePath": "src/components/ui/Rating.tsx", + "lineRange": [ + 17, + 28 + ], + "summary": "Read-only star display rendering five stars with active/inactive styling based on the numeric value prop.", + "tags": [ + "component", + "ui", + "rating" + ], + "complexity": "simple" + }, + { + "id": "function:src/components/ui/Rating.tsx:RatingInput", + "type": "function", + "name": "RatingInput", + "filePath": "src/components/ui/Rating.tsx", + "lineRange": [ + 30, + 51 + ], + "summary": "Interactive star rating input that fires an onChange callback when a star is clicked, supporting a disabled state.", + "tags": [ + "component", + "form", + "rating", + "event-handler" + ], + "complexity": "simple" + }, + { + "id": "file:src/components/ui/Select.tsx", + "type": "file", + "name": "Select.tsx", + "filePath": "src/components/ui/Select.tsx", + "summary": "Labeled select dropdown component with error display and type-safe options list, wrapping the native HTML select element.", + "tags": [ + "component", + "ui", + "form", + "utility" + ], + "complexity": "simple" + }, + { + "id": "function:src/components/ui/Select.tsx:Select", + "type": "function", + "name": "Select", + "filePath": "src/components/ui/Select.tsx", + "lineRange": [ + 15, + 43 + ], + "summary": "Exported select component that renders labeled options with validation error feedback and auto-generated id.", + "tags": [ + "component", + "form", + "ui" + ], + "complexity": "simple" + }, + { + "id": "file:src/components/ui/TextArea.tsx", + "type": "file", + "name": "TextArea.tsx", + "filePath": "src/components/ui/TextArea.tsx", + "summary": "Textarea component with label, error display, and persistent height storage per field name using localStorage.", + "tags": [ + "component", + "ui", + "form", + "utility" + ], + "complexity": "moderate" + }, + { + "id": "function:src/components/ui/TextArea.tsx:TextArea", + "type": "function", + "name": "TextArea", + "filePath": "src/components/ui/TextArea.tsx", + "lineRange": [ + 11, + 64 + ], + "summary": "Exported textarea that restores previously saved height from localStorage on mount and saves on mouse-up drag events.", + "tags": [ + "component", + "form", + "persistence", + "event-handler" + ], + "complexity": "moderate" + }, + { + "id": "file:src/index.css", + "type": "file", + "name": "index.css", + "filePath": "src/index.css", + "summary": "Global stylesheet importing Tailwind CSS base layers and defining custom CSS variables and utility classes for the application theme.", + "tags": [ + "stylesheet", + "tailwind", + "theme", + "configuration" + ], + "complexity": "moderate" + }, + { + "id": "file:src/lib/constants.ts", + "type": "file", + "name": "constants.ts", + "filePath": "src/lib/constants.ts", + "summary": "Application-wide constants mapping status, category, and source enum values to display labels and colors, plus default interview stage definitions.", + "tags": [ + "constants", + "utility", + "configuration", + "data-model" + ], + "complexity": "moderate" + }, + { + "id": "file:src/lib/utils.test.ts", + "type": "file", + "name": "utils.test.ts", + "filePath": "src/lib/utils.test.ts", + "summary": "Unit tests covering formatDate, formatSalaryRange, getTodayDate, getDaysUntil, and isOverdue utility functions.", + "tags": [ + "test", + "utility", + "date", + "formatting" + ], + "complexity": "moderate" + }, + { + "id": "file:src/lib/utils.ts", + "type": "file", + "name": "utils.ts", + "filePath": "src/lib/utils.ts", + "summary": "Pure utility functions for CSS class concatenation, date formatting, salary range display, and deadline calculations used across the UI.", + "tags": [ + "utility", + "formatting", + "date", + "helper" + ], + "complexity": "moderate" + }, + { + "id": "function:src/lib/utils.ts:cn", + "type": "function", + "name": "cn", + "filePath": "src/lib/utils.ts", + "lineRange": [ + 1, + 3 + ], + "summary": "Combines an arbitrary list of CSS class strings, filtering falsy values, into a single space-joined class string.", + "tags": [ + "utility", + "css", + "helper" + ], + "complexity": "simple" + }, + { + "id": "function:src/lib/utils.ts:formatDate", + "type": "function", + "name": "formatDate", + "filePath": "src/lib/utils.ts", + "lineRange": [ + 5, + 24 + ], + "summary": "Parses a YYYY-MM-DD date string into a locale-formatted display string, returning an empty string for null or invalid input.", + "tags": [ + "utility", + "date", + "formatting" + ], + "complexity": "simple" + }, + { + "id": "function:src/lib/utils.ts:formatSalaryRange", + "type": "function", + "name": "formatSalaryRange", + "filePath": "src/lib/utils.ts", + "lineRange": [ + 26, + 42 + ], + "summary": "Formats optional min/max salary numbers into a human-readable range string like '$80K\u2013$120K' or 'Up to $100K'.", + "tags": [ + "utility", + "formatting", + "salary" + ], + "complexity": "simple" + }, + { + "id": "function:src/lib/utils.ts:getDaysUntil", + "type": "function", + "name": "getDaysUntil", + "filePath": "src/lib/utils.ts", + "lineRange": [ + 52, + 63 + ], + "summary": "Calculates the number of days between today and a target YYYY-MM-DD date, returning null for missing input.", + "tags": [ + "utility", + "date", + "deadline" + ], + "complexity": "simple" + }, + { + "id": "function:src/lib/utils.ts:isOverdue", + "type": "function", + "name": "isOverdue", + "filePath": "src/lib/utils.ts", + "lineRange": [ + 65, + 68 + ], + "summary": "Returns true when getDaysUntil produces a negative number, indicating a deadline has passed.", + "tags": [ + "utility", + "date", + "validation" + ], + "complexity": "simple" + }, + { + "id": "file:src/main.tsx", + "type": "file", + "name": "main.tsx", + "filePath": "src/main.tsx", + "summary": "Application entry point that mounts the React 19 root with RouterProvider into the DOM and imports global styles.", + "tags": [ + "entry-point", + "bootstrap", + "router" + ], + "complexity": "simple" + }, + { + "id": "file:src/pages/ApplicationEditPage.tsx", + "type": "file", + "name": "ApplicationEditPage.tsx", + "filePath": "src/pages/ApplicationEditPage.tsx", + "summary": "Full-featured create/edit page for a job application, managing dirty-state blocking, interview stage drafts, delete confirmation, and optimistic saves via the application store.", + "tags": [ + "component", + "page", + "form", + "api-handler" + ], + "complexity": "complex" + }, + { + "id": "function:src/pages/ApplicationEditPage.tsx:ApplicationEditPage", + "type": "function", + "name": "ApplicationEditPage", + "filePath": "src/pages/ApplicationEditPage.tsx", + "lineRange": [ + 11, + 211 + ], + "summary": "Page component that orchestrates create/edit/delete operations for a single application, integrating React Router navigation blocking and multiple Zustand store selectors.", + "tags": [ + "component", + "page", + "form", + "event-handler" + ], + "complexity": "complex" + }, + { + "id": "file:src/pages/ListPage.tsx", + "type": "file", + "name": "ListPage.tsx", + "filePath": "src/pages/ListPage.tsx", + "summary": "Application list page composing filter controls, pagination, and archive/restore/delete actions, backed by the application and filter stores.", + "tags": [ + "component", + "page", + "list", + "filter" + ], + "complexity": "complex" + }, + { + "id": "function:src/pages/ListPage.tsx:ListPage", + "type": "function", + "name": "ListPage", + "filePath": "src/pages/ListPage.tsx", + "lineRange": [ + 10, + 108 + ], + "summary": "Page component that fetches paginated applications, applies filter state, and handles selection, archive, restore, and delete interactions.", + "tags": [ + "component", + "page", + "list", + "event-handler" + ], + "complexity": "complex" + }, + { + "id": "file:src/router.tsx", + "type": "file", + "name": "router.tsx", + "filePath": "src/router.tsx", + "summary": "React Router v7 configuration defining the application's route tree with lazy-loaded page components and a shared app layout.", + "tags": [ + "router", + "configuration", + "entry-point", + "lazy-loading" + ], + "complexity": "simple" + }, + { + "id": "file:src/services/api.ts", + "type": "file", + "name": "api.ts", + "filePath": "src/services/api.ts", + "summary": "HTTP client module for all lambda-api endpoints covering CRUD for applications, interview stages, history versioning, and CSV import/export.", + "tags": [ + "service", + "api-handler", + "http-client", + "serialization" + ], + "complexity": "complex" + }, + { + "id": "class:src/services/api.ts:ApiError", + "type": "class", + "name": "ApiError", + "filePath": "src/services/api.ts", + "lineRange": [ + 15, + 26 + ], + "summary": "Custom Error subclass that carries the HTTP response status code alongside the error message for structured API error handling.", + "tags": [ + "service", + "error-handling", + "utility" + ], + "complexity": "simple" + }, + { + "id": "function:src/services/api.ts:handleResponse", + "type": "function", + "name": "handleResponse", + "filePath": "src/services/api.ts", + "lineRange": [ + 50, + 71 + ], + "summary": "Shared fetch response handler that parses JSON on success or throws an ApiError with the response status and server message on failure.", + "tags": [ + "service", + "error-handling", + "http-client" + ], + "complexity": "moderate" + }, + { + "id": "function:src/services/api.ts:buildQueryString", + "type": "function", + "name": "buildQueryString", + "filePath": "src/services/api.ts", + "lineRange": [ + 28, + 48 + ], + "summary": "Converts a params object into a URL query string, joining array values with commas and skipping null/undefined entries.", + "tags": [ + "service", + "utility", + "serialization" + ], + "complexity": "moderate" + }, + { + "id": "file:src/stores/applicationStore.test.ts", + "type": "file", + "name": "applicationStore.test.ts", + "filePath": "src/stores/applicationStore.test.ts", + "summary": "Unit tests for the applicationStore covering pagination state, filtering, fetch behavior, and CRUD action effects.", + "tags": [ + "test", + "store", + "zustand", + "application" + ], + "complexity": "moderate" + }, + { + "id": "file:src/stores/applicationStore.ts", + "type": "file", + "name": "applicationStore.ts", + "filePath": "src/stores/applicationStore.ts", + "summary": "Zustand store managing the full application list lifecycle including paginated fetching, selection, CRUD actions, and interview stage management via the api service.", + "tags": [ + "store", + "zustand", + "state-management", + "service" + ], + "complexity": "complex" + }, + { + "id": "file:src/stores/filterStore.test.ts", + "type": "file", + "name": "filterStore.test.ts", + "filePath": "src/stores/filterStore.test.ts", + "summary": "Unit tests for the filterStore verifying initial state, individual filter mutations, and combined filter reset behavior.", + "tags": [ + "test", + "store", + "zustand", + "filter" + ], + "complexity": "moderate" + }, + { + "id": "file:src/stores/filterStore.ts", + "type": "file", + "name": "filterStore.ts", + "filePath": "src/stores/filterStore.ts", + "summary": "Zustand store holding search, status, category, source, and sort filter state with a resetFilters action.", + "tags": [ + "store", + "zustand", + "state-management", + "filter" + ], + "complexity": "simple" + }, + { + "id": "file:src/stores/uiStore.test.ts", + "type": "file", + "name": "uiStore.test.ts", + "filePath": "src/stores/uiStore.test.ts", + "summary": "Unit tests for the uiStore verifying theme and view-mode initial values, toggling behavior, and panel open/close actions.", + "tags": [ + "test", + "store", + "zustand", + "ui" + ], + "complexity": "simple" + }, + { + "id": "file:src/stores/uiStore.ts", + "type": "file", + "name": "uiStore.ts", + "filePath": "src/stores/uiStore.ts", + "summary": "Zustand store managing UI-level state: dark/light theme toggle, grid/list view mode, and side-panel open state, persisting theme and view preferences to localStorage.", + "tags": [ + "store", + "zustand", + "state-management", + "ui" + ], + "complexity": "moderate" + }, + { + "id": "file:src/test/setup.ts", + "type": "file", + "name": "setup.ts", + "filePath": "src/test/setup.ts", + "summary": "Vitest global test setup file that imports @testing-library/jest-dom matchers for DOM assertion support.", + "tags": [ + "test", + "configuration", + "setup" + ], + "complexity": "simple" + }, + { + "id": "file:src/types/application.ts", + "type": "file", + "name": "application.ts", + "filePath": "src/types/application.ts", + "summary": "Central TypeScript type definitions for all application-domain entities including Application, InterviewStage, status/category/source enums, and API request/response shapes.", + "tags": [ + "type-definition", + "data-model", + "api-schema", + "domain" + ], + "complexity": "complex" + }, + { + "id": "config:tsconfig.app.json", + "type": "config", + "name": "tsconfig.app.json", + "filePath": "tsconfig.app.json", + "summary": "TypeScript compiler configuration for the React application source with strict mode, JSX preserve, and Vite-compatible bundler module resolution.", + "tags": [ + "configuration", + "typescript", + "build-system" + ], + "complexity": "simple" + }, + { + "id": "config:tsconfig.json", + "type": "config", + "name": "tsconfig.json", + "filePath": "tsconfig.json", + "summary": "Root TypeScript project configuration referencing tsconfig.app.json and tsconfig.node.json as composite project references.", + "tags": [ + "configuration", + "typescript", + "build-system" + ], + "complexity": "simple" + }, + { + "id": "config:tsconfig.node.json", + "type": "config", + "name": "tsconfig.node.json", + "filePath": "tsconfig.node.json", + "summary": "TypeScript configuration for Node.js build tooling (vite.config.ts) with ESNext module resolution and composite build support.", + "tags": [ + "configuration", + "typescript", + "build-system" + ], + "complexity": "simple" + }, + { + "id": "file:vite.config.ts", + "type": "file", + "name": "vite.config.ts", + "filePath": "vite.config.ts", + "summary": "Vite 7 build configuration enabling the React plugin, Tailwind CSS 4 integration, and Vitest test runner with jsdom environment.", + "tags": [ + "configuration", + "build-system", + "vite", + "test" + ], + "complexity": "simple", + "languageNotes": "Combines Vite build config and Vitest test config in a single file using the defineConfig pattern with an inline `test` block." + } + ], + "edges": [ + { + "source": "document:README.md", + "target": "file:src/App.tsx", + "type": "documents", + "direction": "forward", + "weight": 0.5 + }, + { + "source": "file:src/App.tsx", + "target": "function:src/App.tsx:App", + "type": "contains", + "direction": "forward", + "weight": 1.0 + }, + { + "source": "file:src/App.tsx", + "target": "function:src/App.tsx:App", + "type": "exports", + "direction": "forward", + "weight": 0.8 + }, + { + "source": "file:src/components/applications/ApplicationCard.test.tsx", + "target": "file:src/components/applications/ApplicationCard.tsx", + "type": "imports", + "direction": "forward", + "weight": 0.7 + }, + { + "source": "file:src/components/applications/ApplicationCard.tsx", + "target": "function:src/components/applications/ApplicationCard.tsx:ApplicationCard", + "type": "contains", + "direction": "forward", + "weight": 1.0 + }, + { + "source": "file:src/components/applications/ApplicationCard.tsx", + "target": "function:src/components/applications/ApplicationCard.tsx:ApplicationCard", + "type": "exports", + "direction": "forward", + "weight": 0.8 + }, + { + "source": "file:src/components/applications/ApplicationCard.tsx", + "target": "file:src/components/applications/ApplicationCard.test.tsx", + "type": "tested_by", + "direction": "forward", + "weight": 0.5 + }, + { + "source": "file:src/components/applications/ApplicationForm.test.tsx", + "target": "file:src/components/applications/ApplicationForm.tsx", + "type": "imports", + "direction": "forward", + "weight": 0.7 + }, + { + "source": "file:src/components/applications/ApplicationForm.tsx", + "target": "function:src/components/applications/ApplicationForm.tsx:ApplicationForm", + "type": "contains", + "direction": "forward", + "weight": 1.0 + }, + { + "source": "file:src/components/applications/ApplicationForm.tsx", + "target": "function:src/components/applications/ApplicationForm.tsx:ApplicationForm", + "type": "exports", + "direction": "forward", + "weight": 0.8 + }, + { + "source": "file:src/components/applications/ApplicationForm.tsx", + "target": "function:src/components/applications/ApplicationForm.tsx:normalizeInitialValues", + "type": "contains", + "direction": "forward", + "weight": 1.0 + }, + { + "source": "file:src/components/applications/ApplicationForm.tsx", + "target": "file:src/components/applications/ApplicationForm.test.tsx", + "type": "tested_by", + "direction": "forward", + "weight": 0.5 + }, + { + "source": "file:src/components/applications/ApplicationList.tsx", + "target": "file:src/components/applications/ApplicationCard.tsx", + "type": "imports", + "direction": "forward", + "weight": 0.7 + }, + { + "source": "file:src/components/applications/ApplicationList.tsx", + "target": "function:src/components/applications/ApplicationList.tsx:ApplicationList", + "type": "contains", + "direction": "forward", + "weight": 1.0 + }, + { + "source": "file:src/components/applications/ApplicationList.tsx", + "target": "function:src/components/applications/ApplicationList.tsx:ApplicationList", + "type": "exports", + "direction": "forward", + "weight": 0.8 + }, + { + "source": "file:src/components/applications/CsvImportModal.tsx", + "target": "function:src/components/applications/CsvImportModal.tsx:CsvImportModal", + "type": "contains", + "direction": "forward", + "weight": 1.0 + }, + { + "source": "file:src/components/applications/CsvImportModal.tsx", + "target": "function:src/components/applications/CsvImportModal.tsx:CsvImportModal", + "type": "exports", + "direction": "forward", + "weight": 0.8 + }, + { + "source": "file:src/components/applications/FieldDiff.tsx", + "target": "function:src/components/applications/FieldDiff.tsx:FieldDiff", + "type": "contains", + "direction": "forward", + "weight": 1.0 + }, + { + "source": "file:src/components/applications/FieldDiff.tsx", + "target": "function:src/components/applications/FieldDiff.tsx:FieldDiff", + "type": "exports", + "direction": "forward", + "weight": 0.8 + }, + { + "source": "file:src/components/applications/FilterBar.test.tsx", + "target": "file:src/components/applications/FilterBar.tsx", + "type": "imports", + "direction": "forward", + "weight": 0.7 + }, + { + "source": "file:src/components/applications/FilterBar.tsx", + "target": "function:src/components/applications/FilterBar.tsx:FilterBar", + "type": "contains", + "direction": "forward", + "weight": 1.0 + }, + { + "source": "file:src/components/applications/FilterBar.tsx", + "target": "function:src/components/applications/FilterBar.tsx:FilterBar", + "type": "exports", + "direction": "forward", + "weight": 0.8 + }, + { + "source": "file:src/components/applications/FilterBar.tsx", + "target": "file:src/components/applications/FilterBar.test.tsx", + "type": "tested_by", + "direction": "forward", + "weight": 0.5 + }, + { + "source": "file:src/components/applications/HistoryPanel.tsx", + "target": "file:src/components/applications/FieldDiff.tsx", + "type": "imports", + "direction": "forward", + "weight": 0.7 + }, + { + "source": "file:src/components/applications/HistoryPanel.tsx", + "target": "function:src/components/applications/HistoryPanel.tsx:HistoryPanel", + "type": "contains", + "direction": "forward", + "weight": 1.0 + }, + { + "source": "file:src/components/applications/HistoryPanel.tsx", + "target": "function:src/components/applications/HistoryPanel.tsx:HistoryPanel", + "type": "exports", + "direction": "forward", + "weight": 0.8 + }, + { + "source": "file:src/components/interviews/InterviewStageItem.tsx", + "target": "file:src/components/interviews/InterviewStageForm.tsx", + "type": "imports", + "direction": "forward", + "weight": 0.7 + }, + { + "source": "file:src/components/interviews/InterviewStageList.tsx", + "target": "file:src/components/interviews/InterviewStageForm.tsx", + "type": "imports", + "direction": "forward", + "weight": 0.7 + }, + { + "source": "file:src/components/interviews/InterviewStageList.tsx", + "target": "file:src/components/interviews/InterviewStageItem.tsx", + "type": "imports", + "direction": "forward", + "weight": 0.7 + }, + { + "source": "file:src/components/interviews/InterviewStageForm.tsx", + "target": "function:src/components/interviews/InterviewStageForm.tsx:InterviewStageForm", + "type": "contains", + "direction": "forward", + "weight": 1.0 + }, + { + "source": "file:src/components/interviews/InterviewStageForm.tsx", + "target": "function:src/components/interviews/InterviewStageForm.tsx:InterviewStageForm", + "type": "exports", + "direction": "forward", + "weight": 0.8 + }, + { + "source": "file:src/components/interviews/InterviewStageItem.tsx", + "target": "function:src/components/interviews/InterviewStageItem.tsx:InterviewStageItem", + "type": "contains", + "direction": "forward", + "weight": 1.0 + }, + { + "source": "file:src/components/interviews/InterviewStageItem.tsx", + "target": "function:src/components/interviews/InterviewStageItem.tsx:InterviewStageItem", + "type": "exports", + "direction": "forward", + "weight": 0.8 + }, + { + "source": "file:src/components/interviews/InterviewStageList.tsx", + "target": "function:src/components/interviews/InterviewStageList.tsx:InterviewStageList", + "type": "contains", + "direction": "forward", + "weight": 1.0 + }, + { + "source": "file:src/components/interviews/InterviewStageList.tsx", + "target": "function:src/components/interviews/InterviewStageList.tsx:InterviewStageList", + "type": "exports", + "direction": "forward", + "weight": 0.8 + }, + { + "source": "file:src/components/layout/ContextPanel.tsx", + "target": "function:src/components/layout/ContextPanel.tsx:ContextPanel", + "type": "contains", + "direction": "forward", + "weight": 1.0 + }, + { + "source": "file:src/components/layout/ContextPanel.tsx", + "target": "function:src/components/layout/ContextPanel.tsx:ContextPanel", + "type": "exports", + "direction": "forward", + "weight": 0.8 + }, + { + "source": "file:src/components/layout/PipelineSummaryBar.tsx", + "target": "function:src/components/layout/PipelineSummaryBar.tsx:PipelineSummaryBar", + "type": "contains", + "direction": "forward", + "weight": 1.0 + }, + { + "source": "file:src/components/layout/PipelineSummaryBar.tsx", + "target": "function:src/components/layout/PipelineSummaryBar.tsx:PipelineSummaryBar", + "type": "exports", + "direction": "forward", + "weight": 0.8 + }, + { + "source": "file:src/components/layout/Sidebar.tsx", + "target": "function:src/components/layout/Sidebar.tsx:Sidebar", + "type": "contains", + "direction": "forward", + "weight": 1.0 + }, + { + "source": "file:src/components/layout/Sidebar.tsx", + "target": "function:src/components/layout/Sidebar.tsx:Sidebar", + "type": "exports", + "direction": "forward", + "weight": 0.8 + }, + { + "source": "file:src/components/ui/Badge.tsx", + "target": "function:src/components/ui/Badge.tsx:Badge", + "type": "contains", + "direction": "forward", + "weight": 1.0 + }, + { + "source": "file:src/components/ui/Badge.tsx", + "target": "function:src/components/ui/Badge.tsx:Badge", + "type": "exports", + "direction": "forward", + "weight": 0.8 + }, + { + "source": "file:src/components/ui/Button.tsx", + "target": "function:src/components/ui/Button.tsx:Button", + "type": "contains", + "direction": "forward", + "weight": 1.0 + }, + { + "source": "file:src/components/ui/Button.tsx", + "target": "function:src/components/ui/Button.tsx:Button", + "type": "exports", + "direction": "forward", + "weight": 0.8 + }, + { + "source": "file:src/components/ui/ConfirmDialog.test.tsx", + "target": "file:src/components/ui/ConfirmDialog.tsx", + "type": "imports", + "direction": "forward", + "weight": 0.7 + }, + { + "source": "file:src/components/ui/ConfirmDialog.tsx", + "target": "file:src/components/ui/Button.tsx", + "type": "imports", + "direction": "forward", + "weight": 0.7 + }, + { + "source": "file:src/components/ui/ConfirmDialog.tsx", + "target": "file:src/components/ui/Modal.tsx", + "type": "imports", + "direction": "forward", + "weight": 0.7 + }, + { + "source": "file:src/components/ui/ConfirmDialog.tsx", + "target": "function:src/components/ui/ConfirmDialog.tsx:ConfirmDialog", + "type": "contains", + "direction": "forward", + "weight": 1.0 + }, + { + "source": "file:src/components/ui/ConfirmDialog.tsx", + "target": "function:src/components/ui/ConfirmDialog.tsx:ConfirmDialog", + "type": "exports", + "direction": "forward", + "weight": 0.8 + }, + { + "source": "file:src/components/ui/ConfirmDialog.tsx", + "target": "file:src/components/ui/ConfirmDialog.test.tsx", + "type": "tested_by", + "direction": "forward", + "weight": 0.5 + }, + { + "source": "file:src/components/ui/EmptyState.tsx", + "target": "function:src/components/ui/EmptyState.tsx:EmptyState", + "type": "contains", + "direction": "forward", + "weight": 1.0 + }, + { + "source": "file:src/components/ui/EmptyState.tsx", + "target": "function:src/components/ui/EmptyState.tsx:EmptyState", + "type": "exports", + "direction": "forward", + "weight": 0.8 + }, + { + "source": "config:package.json", + "target": "file:src/App.tsx", + "type": "configures", + "direction": "forward", + "weight": 0.6 + }, + { + "source": "file:src/components/ui/Input.tsx", + "target": "function:src/components/ui/Input.tsx:Input", + "type": "contains", + "direction": "forward", + "weight": 1.0 + }, + { + "source": "file:src/components/ui/Input.tsx", + "target": "function:src/components/ui/Input.tsx:Input", + "type": "exports", + "direction": "forward", + "weight": 0.8 + }, + { + "source": "file:src/components/ui/Modal.tsx", + "target": "function:src/components/ui/Modal.tsx:Modal", + "type": "contains", + "direction": "forward", + "weight": 1.0 + }, + { + "source": "file:src/components/ui/Modal.tsx", + "target": "function:src/components/ui/Modal.tsx:Modal", + "type": "exports", + "direction": "forward", + "weight": 0.8 + }, + { + "source": "file:src/components/ui/Pagination.test.tsx", + "target": "file:src/components/ui/Pagination.tsx", + "type": "imports", + "direction": "forward", + "weight": 0.7 + }, + { + "source": "file:src/components/ui/Pagination.tsx", + "target": "function:src/components/ui/Pagination.tsx:Pagination", + "type": "contains", + "direction": "forward", + "weight": 1.0 + }, + { + "source": "file:src/components/ui/Pagination.tsx", + "target": "function:src/components/ui/Pagination.tsx:Pagination", + "type": "exports", + "direction": "forward", + "weight": 0.8 + }, + { + "source": "file:src/components/ui/Pagination.tsx", + "target": "file:src/components/ui/Button.tsx", + "type": "imports", + "direction": "forward", + "weight": 0.7 + }, + { + "source": "file:src/components/ui/Rating.tsx", + "target": "function:src/components/ui/Rating.tsx:RatingDisplay", + "type": "contains", + "direction": "forward", + "weight": 1.0 + }, + { + "source": "file:src/components/ui/Rating.tsx", + "target": "function:src/components/ui/Rating.tsx:RatingInput", + "type": "contains", + "direction": "forward", + "weight": 1.0 + }, + { + "source": "file:src/components/ui/Rating.tsx", + "target": "function:src/components/ui/Rating.tsx:RatingDisplay", + "type": "exports", + "direction": "forward", + "weight": 0.8 + }, + { + "source": "file:src/components/ui/Rating.tsx", + "target": "function:src/components/ui/Rating.tsx:RatingInput", + "type": "exports", + "direction": "forward", + "weight": 0.8 + }, + { + "source": "file:src/components/ui/Rating.tsx", + "target": "file:src/components/ui/Button.tsx", + "type": "imports", + "direction": "forward", + "weight": 0.7 + }, + { + "source": "file:src/components/ui/Select.tsx", + "target": "function:src/components/ui/Select.tsx:Select", + "type": "contains", + "direction": "forward", + "weight": 1.0 + }, + { + "source": "file:src/components/ui/Select.tsx", + "target": "function:src/components/ui/Select.tsx:Select", + "type": "exports", + "direction": "forward", + "weight": 0.8 + }, + { + "source": "file:src/components/ui/TextArea.tsx", + "target": "function:src/components/ui/TextArea.tsx:TextArea", + "type": "contains", + "direction": "forward", + "weight": 1.0 + }, + { + "source": "file:src/components/ui/TextArea.tsx", + "target": "function:src/components/ui/TextArea.tsx:TextArea", + "type": "exports", + "direction": "forward", + "weight": 0.8 + }, + { + "source": "file:src/main.tsx", + "target": "file:src/index.css", + "type": "imports", + "direction": "forward", + "weight": 0.7 + }, + { + "source": "file:src/lib/utils.test.ts", + "target": "file:src/lib/utils.ts", + "type": "imports", + "direction": "forward", + "weight": 0.7 + }, + { + "source": "file:src/lib/utils.ts", + "target": "function:src/lib/utils.ts:cn", + "type": "contains", + "direction": "forward", + "weight": 1.0 + }, + { + "source": "file:src/lib/utils.ts", + "target": "function:src/lib/utils.ts:formatDate", + "type": "contains", + "direction": "forward", + "weight": 1.0 + }, + { + "source": "file:src/lib/utils.ts", + "target": "function:src/lib/utils.ts:formatSalaryRange", + "type": "contains", + "direction": "forward", + "weight": 1.0 + }, + { + "source": "file:src/lib/utils.ts", + "target": "function:src/lib/utils.ts:getDaysUntil", + "type": "contains", + "direction": "forward", + "weight": 1.0 + }, + { + "source": "file:src/lib/utils.ts", + "target": "function:src/lib/utils.ts:isOverdue", + "type": "contains", + "direction": "forward", + "weight": 1.0 + }, + { + "source": "file:src/lib/utils.ts", + "target": "function:src/lib/utils.ts:cn", + "type": "exports", + "direction": "forward", + "weight": 0.8 + }, + { + "source": "file:src/lib/utils.ts", + "target": "function:src/lib/utils.ts:formatDate", + "type": "exports", + "direction": "forward", + "weight": 0.8 + }, + { + "source": "file:src/lib/utils.ts", + "target": "function:src/lib/utils.ts:formatSalaryRange", + "type": "exports", + "direction": "forward", + "weight": 0.8 + }, + { + "source": "file:src/lib/utils.ts", + "target": "function:src/lib/utils.ts:getDaysUntil", + "type": "exports", + "direction": "forward", + "weight": 0.8 + }, + { + "source": "file:src/lib/utils.ts", + "target": "function:src/lib/utils.ts:isOverdue", + "type": "exports", + "direction": "forward", + "weight": 0.8 + }, + { + "source": "file:src/lib/utils.ts", + "target": "file:src/lib/utils.test.ts", + "type": "tested_by", + "direction": "forward", + "weight": 0.5 + }, + { + "source": "file:src/pages/ApplicationEditPage.tsx", + "target": "function:src/pages/ApplicationEditPage.tsx:ApplicationEditPage", + "type": "contains", + "direction": "forward", + "weight": 1.0 + }, + { + "source": "file:src/pages/ApplicationEditPage.tsx", + "target": "function:src/pages/ApplicationEditPage.tsx:ApplicationEditPage", + "type": "exports", + "direction": "forward", + "weight": 0.8 + }, + { + "source": "file:src/pages/ListPage.tsx", + "target": "function:src/pages/ListPage.tsx:ListPage", + "type": "contains", + "direction": "forward", + "weight": 1.0 + }, + { + "source": "file:src/pages/ListPage.tsx", + "target": "function:src/pages/ListPage.tsx:ListPage", + "type": "exports", + "direction": "forward", + "weight": 0.8 + }, + { + "source": "file:src/services/api.ts", + "target": "class:src/services/api.ts:ApiError", + "type": "contains", + "direction": "forward", + "weight": 1.0 + }, + { + "source": "file:src/services/api.ts", + "target": "class:src/services/api.ts:ApiError", + "type": "exports", + "direction": "forward", + "weight": 0.8 + }, + { + "source": "file:src/services/api.ts", + "target": "function:src/services/api.ts:handleResponse", + "type": "contains", + "direction": "forward", + "weight": 1.0 + }, + { + "source": "file:src/services/api.ts", + "target": "function:src/services/api.ts:handleResponse", + "type": "exports", + "direction": "forward", + "weight": 0.8 + }, + { + "source": "file:src/services/api.ts", + "target": "function:src/services/api.ts:buildQueryString", + "type": "contains", + "direction": "forward", + "weight": 1.0 + }, + { + "source": "file:src/services/api.ts", + "target": "function:src/services/api.ts:buildQueryString", + "type": "exports", + "direction": "forward", + "weight": 0.8 + }, + { + "source": "file:src/stores/applicationStore.test.ts", + "target": "file:src/stores/applicationStore.ts", + "type": "imports", + "direction": "forward", + "weight": 0.7 + }, + { + "source": "file:src/stores/applicationStore.ts", + "target": "file:src/stores/applicationStore.test.ts", + "type": "tested_by", + "direction": "forward", + "weight": 0.5 + }, + { + "source": "file:src/stores/filterStore.test.ts", + "target": "file:src/stores/filterStore.ts", + "type": "imports", + "direction": "forward", + "weight": 0.7 + }, + { + "source": "file:src/stores/filterStore.ts", + "target": "file:src/stores/filterStore.test.ts", + "type": "tested_by", + "direction": "forward", + "weight": 0.5 + }, + { + "source": "file:src/stores/uiStore.test.ts", + "target": "file:src/stores/uiStore.ts", + "type": "imports", + "direction": "forward", + "weight": 0.7 + }, + { + "source": "file:src/stores/uiStore.ts", + "target": "file:src/stores/uiStore.test.ts", + "type": "tested_by", + "direction": "forward", + "weight": 0.5 + }, + { + "source": "config:tsconfig.json", + "target": "config:tsconfig.app.json", + "type": "configures", + "direction": "forward", + "weight": 0.6 + }, + { + "source": "config:tsconfig.json", + "target": "config:tsconfig.node.json", + "type": "configures", + "direction": "forward", + "weight": 0.6 + }, + { + "source": "config:tsconfig.app.json", + "target": "file:src/main.tsx", + "type": "configures", + "direction": "forward", + "weight": 0.6 + }, + { + "source": "config:tsconfig.node.json", + "target": "file:vite.config.ts", + "type": "configures", + "direction": "forward", + "weight": 0.6 + }, + { + "source": "file:vite.config.ts", + "target": "file:src/main.tsx", + "type": "depends_on", + "direction": "forward", + "weight": 0.6 + } + ], + "layers": [ + { + "id": "layer:ui", + "name": "UI Layer", + "description": "React components for rendering job applications, interview stages, layout shells, and reusable primitive widgets used throughout the SPA.", + "nodeIds": [ + "file:src/components/applications/ApplicationCard.tsx", + "file:src/components/applications/ApplicationForm.tsx", + "file:src/components/applications/ApplicationList.tsx", + "file:src/components/applications/CsvImportModal.tsx", + "file:src/components/applications/FieldDiff.tsx", + "file:src/components/applications/FilterBar.tsx", + "file:src/components/applications/HistoryPanel.tsx", + "file:src/components/interviews/InterviewStageForm.tsx", + "file:src/components/interviews/InterviewStageItem.tsx", + "file:src/components/interviews/InterviewStageList.tsx", + "file:src/components/layout/ContextPanel.tsx", + "file:src/components/layout/PipelineSummaryBar.tsx", + "file:src/components/layout/Sidebar.tsx", + "file:src/components/ui/Badge.tsx", + "file:src/components/ui/Button.tsx", + "file:src/components/ui/ConfirmDialog.tsx", + "file:src/components/ui/EmptyState.tsx", + "file:src/components/ui/Input.tsx", + "file:src/components/ui/Modal.tsx", + "file:src/components/ui/Pagination.tsx", + "file:src/components/ui/Rating.tsx", + "file:src/components/ui/Select.tsx", + "file:src/components/ui/TextArea.tsx", + "file:src/pages/ApplicationEditPage.tsx", + "file:src/pages/ListPage.tsx" + ] + }, + { + "id": "layer:service", + "name": "Service Layer", + "description": "HTTP client for the lambda-api backend and Zustand stores managing application list, filter, and UI state across the SPA.", + "nodeIds": [ + "file:src/services/api.ts", + "file:src/stores/applicationStore.ts", + "file:src/stores/filterStore.ts", + "file:src/stores/uiStore.ts" + ] + }, + { + "id": "layer:types", + "name": "Types Layer", + "description": "Central TypeScript domain type definitions for job applications, interview stages, enums, and API request/response contracts shared across all layers.", + "nodeIds": [ + "file:src/types/application.ts" + ] + }, + { + "id": "layer:utility", + "name": "Utility Layer", + "description": "Pure helper functions for date formatting, salary display, and deadline calculations, plus application-wide constants and the global Tailwind CSS stylesheet.", + "nodeIds": [ + "file:src/lib/constants.ts", + "file:src/lib/utils.ts", + "file:src/index.css" + ] + }, + { + "id": "layer:config", + "name": "Config Layer", + "description": "Application bootstrap and wiring files (React root, router setup, App shell) alongside all project-level build and tooling configuration for Vite, TypeScript, ESLint, and PostCSS.", + "nodeIds": [ + "file:src/App.tsx", + "file:src/main.tsx", + "file:src/router.tsx", + "config:.auditconfig.json", + "file:eslint.config.js", + "file:index.html", + "config:package.json", + "file:postcss.config.js", + "config:tsconfig.app.json", + "config:tsconfig.json", + "config:tsconfig.node.json", + "file:vite.config.ts" + ] + }, + { + "id": "layer:test", + "name": "Test Layer", + "description": "Vitest unit tests for components, stores, and utilities, plus the global test setup file that configures jest-dom matchers.", + "nodeIds": [ + "file:src/components/applications/ApplicationCard.test.tsx", + "file:src/components/applications/ApplicationForm.test.tsx", + "file:src/components/applications/FilterBar.test.tsx", + "file:src/components/ui/ConfirmDialog.test.tsx", + "file:src/components/ui/Pagination.test.tsx", + "file:src/lib/utils.test.ts", + "file:src/stores/applicationStore.test.ts", + "file:src/stores/filterStore.test.ts", + "file:src/stores/uiStore.test.ts", + "file:src/test/setup.ts" + ] + }, + { + "id": "layer:documentation", + "name": "Documentation", + "description": "Developer guide covering ports, prerequisites, and build/test instructions for the lambda-react-ui SPA.", + "nodeIds": [ + "document:README.md" + ] + } + ], + "tour": [ + { + "order": 1, + "title": "Project Overview", + "description": "The README establishes the project's identity: a React 19 + Vite 7 + Zustand + Tailwind CSS 4 SPA that tracks job applications, running on port 3090 and talking to the lambda-api backend (Hono + DynamoDB) on port 5090. Reading it first answers the \"what and why\" before diving into code, and sets the mental model of a frontend-only SPA that owns no data persistence of its own.", + "nodeIds": [ + "document:README.md" + ] + }, + { + "order": 2, + "title": "Build Tooling & Project Manifest", + "description": "The package.json and vite.config.ts are the project's heartbeat: package.json declares React 19, Tailwind CSS 4, and Zustand as runtime dependencies and scripts like dev, build, and test, while vite.config.ts wires together the React plugin, Tailwind CSS 4 integration, and the Vitest test runner with a jsdom environment. Together these two files explain how the project starts, compiles, and tests itself before a single line of application code runs.", + "nodeIds": [ + "config:package.json", + "file:vite.config.ts" + ], + "languageLesson": "Vite uses native ES modules during development for near-instant HMR, and bundles with Rollup for production. The @vitejs/plugin-react plugin injects Fast Refresh so React components hot-reload without losing component state." + }, + { + "order": 3, + "title": "HTML Shell & CSS Entry", + "description": "index.html is the single page that Vite serves \u2014 it contains a bare #root div and a script tag pointing at src/main.tsx, which is the only JavaScript on the page at load time. The global stylesheet src/index.css imports Tailwind's base layers and defines custom CSS variables for the app's light/dark theme, establishing the visual foundation every component inherits.", + "nodeIds": [ + "file:index.html", + "file:src/index.css" + ], + "languageLesson": "Tailwind CSS 4 uses a CSS-native @import and @theme approach rather than a JS config file. Custom design tokens are declared as CSS custom properties inside @theme { }, making them available as Tailwind utilities automatically." + }, + { + "order": 4, + "title": "Application Bootstrap", + "description": "src/main.tsx is the true code entry point: it calls React 19's createRoot, wraps the tree in a RouterProvider, and imports global styles. This is the smallest possible bootstrapping file by design \u2014 all routing logic is delegated to src/router.tsx and all component hierarchy starts with src/App.tsx, keeping main.tsx as a thin mount point.", + "nodeIds": [ + "file:src/main.tsx", + "file:src/router.tsx" + ], + "languageLesson": "React 19's createRoot replaces the legacy ReactDOM.render API. The RouterProvider pattern (React Router v7) moves the router instance outside the component tree, enabling type-safe route definitions, data loading, and lazy-loaded code-split pages." + }, + { + "order": 5, + "title": "App Shell & Layout", + "description": "src/App.tsx is the root component rendered inside RouterProvider. It composes the app's outer shell \u2014 Sidebar for navigation, PipelineSummaryBar for at-a-glance status counts, and the main content outlet \u2014 and conditionally renders the ContextPanel slide-over based on a flag from the Zustand UI store. This file is the clearest single place to understand the app's visual structure before exploring individual panels.", + "nodeIds": [ + "file:src/App.tsx", + "file:src/components/layout/Sidebar.tsx", + "file:src/components/layout/PipelineSummaryBar.tsx" + ] + }, + { + "order": 6, + "title": "Domain Types", + "description": "src/types/application.ts is the shared vocabulary of the entire codebase. It defines the Application entity, the InterviewStage sub-entity, and the status, category, and source enums that appear in every store, component, and API call. Understanding these types is prerequisite to reading any feature code \u2014 they form the contract between the frontend and the lambda-api backend.", + "nodeIds": [ + "file:src/types/application.ts" + ], + "languageLesson": "TypeScript discriminated unions (e.g. status enums) let the compiler narrow types in conditionals without runtime checks. Defining all API request/response shapes here means any server-contract change surfaces as a compile error across every file that imports the type." + }, + { + "order": 7, + "title": "Constants & Utilities", + "description": "src/lib/constants.ts maps every enum value to its display label and color class, and defines the default interview stage definitions seeded into new applications. src/lib/utils.ts provides pure functions for CSS class concatenation (cn), date formatting, salary range display, and deadline overdue calculations. These two files are the utility layer that every component reaches into \u2014 they contain no React, no state, and no side effects.", + "nodeIds": [ + "file:src/lib/constants.ts", + "file:src/lib/utils.ts" + ] + }, + { + "order": 8, + "title": "API Service Layer", + "description": "src/services/api.ts is the single HTTP boundary between the SPA and the lambda-api backend. It exports typed async functions for every operation: listing, creating, updating, archiving, and deleting applications; managing interview stages; fetching paginated revision history; and POSTing CSV files for bulk import. Centralising all fetch calls here means the rest of the codebase never constructs URLs or parses responses directly.", + "nodeIds": [ + "file:src/services/api.ts" + ] + }, + { + "order": 9, + "title": "State Management: Three Stores", + "description": "The app's runtime state lives in three Zustand stores. applicationStore.ts orchestrates the full CRUD lifecycle \u2014 fetching paginated lists, tracking the selected application, and delegating to api.ts for mutations. filterStore.ts holds the search text, status, category, source, and sort selections used to query the list. uiStore.ts manages cross-cutting presentation state: dark/light theme, grid vs. list view mode, and whether the ContextPanel is open, persisting theme and view to localStorage.", + "nodeIds": [ + "file:src/stores/applicationStore.ts", + "file:src/stores/filterStore.ts", + "file:src/stores/uiStore.ts" + ], + "languageLesson": "Zustand stores are plain JS objects with a set function \u2014 no reducers, no actions, no provider wrappers required. Selective subscriptions (useStore(s => s.field)) prevent re-renders when unrelated slices change, giving fine-grained reactivity with minimal boilerplate." + }, + { + "order": 10, + "title": "Primitive UI Components", + "description": "The src/components/ui/ folder is the design system layer: Button (the most-imported file in the codebase with fan-in 3) provides variant and size props; Modal renders into a React portal with Escape-key handling; ConfirmDialog composes Modal and Button into a reusable destructive-action guard; Input, Select, TextArea, and Rating provide consistently styled, accessible form primitives. These primitives are used throughout every feature and should be understood before reading the feature components that compose them.", + "nodeIds": [ + "file:src/components/ui/Button.tsx", + "file:src/components/ui/Modal.tsx", + "file:src/components/ui/ConfirmDialog.tsx", + "file:src/components/ui/Badge.tsx" + ], + "languageLesson": "React portals (ReactDOM.createPortal) render a subtree into a DOM node outside the component hierarchy \u2014 typically document.body. This is the standard technique for modals and tooltips that must escape CSS overflow:hidden or z-index stacking contexts set by ancestor elements." + }, + { + "order": 11, + "title": "Application List & Filtering", + "description": "The list view is composed of two components that work together: FilterBar renders the toolbar with status/category/source selectors and CSV import/export triggers, writing selections into filterStore; ApplicationList reads from applicationStore to render a responsive grid or list of ApplicationCard components, with per-card context menus and archive/delete confirmation dialogs. This pair is what users interact with most on the main screen.", + "nodeIds": [ + "file:src/components/applications/FilterBar.tsx", + "file:src/components/applications/ApplicationList.tsx", + "file:src/components/applications/ApplicationCard.tsx" + ] + }, + { + "order": 12, + "title": "Pages: List & Edit", + "description": "src/pages/ListPage.tsx and src/pages/ApplicationEditPage.tsx are the two routable screens. ListPage composes FilterBar, ApplicationList, and Pagination into the main view, wiring filter state changes to store refreshes. ApplicationEditPage handles the full create/edit flow: it guards unsaved changes with a dirty-state blocker, manages interview stage drafts locally before committing, confirms deletes, and calls the application store for optimistic saves.", + "nodeIds": [ + "file:src/pages/ListPage.tsx", + "file:src/pages/ApplicationEditPage.tsx" + ] + }, + { + "order": 13, + "title": "Application Form & Interview Stages", + "description": "ApplicationForm.tsx is the largest single component \u2014 it owns inline validation, status transition logic, salary fields, URL normalization, and embeds an InterviewStageList directly so stages can be drafted before the application is saved. The InterviewStageList/Item/Form trio manages the ordered list of interview rounds: InterviewStageList renders the full list with a progress bar and an \"add\" trigger, InterviewStageItem provides an inline edit toggle, and InterviewStageForm is the shared add/edit form reused by both.", + "nodeIds": [ + "file:src/components/applications/ApplicationForm.tsx", + "file:src/components/interviews/InterviewStageList.tsx", + "file:src/components/interviews/InterviewStageItem.tsx", + "file:src/components/interviews/InterviewStageForm.tsx" + ] + }, + { + "order": 14, + "title": "Context Panel & History", + "description": "ContextPanel.tsx is the right-side slide-over that appears when a user selects an application from the list. It orchestrates three tabs: a detail view with inline field editing, a link to the full edit page, and a history tab. HistoryPanel loads paginated revision history from the API and renders each version as a set of FieldDiff rows (before/after for each changed field), with a restore-to-version action \u2014 giving users a full audit trail of every change.", + "nodeIds": [ + "file:src/components/layout/ContextPanel.tsx", + "file:src/components/applications/HistoryPanel.tsx", + "file:src/components/applications/FieldDiff.tsx" + ] + }, + { + "order": 15, + "title": "CSV Import & TypeScript Config", + "description": "CsvImportModal.tsx rounds out the data-management features: it allows bulk import via CSV upload, shows per-row error reporting and an import result summary, and offers a sample CSV download. The TypeScript configuration trio (tsconfig.json as the composite root, tsconfig.app.json for source with strict mode and Vite bundler resolution, tsconfig.node.json for build tooling) ensures the compiler applies the right settings to each part of the project and enables incremental builds.", + "nodeIds": [ + "file:src/components/applications/CsvImportModal.tsx", + "config:tsconfig.json", + "config:tsconfig.app.json" + ], + "languageLesson": "TypeScript composite projects (references in tsconfig.json) split the compilation into independent units with their own settings. tsconfig.app.json uses moduleResolution: bundler, which resolves imports the way Vite does rather than following classic Node resolution rules \u2014 avoiding spurious 'cannot find module' errors for path aliases and extension-less imports." + } + ] +} \ No newline at end of file diff --git a/lambda-react-ui/.understand-anything/meta.json b/lambda-react-ui/.understand-anything/meta.json new file mode 100644 index 00000000..0d850fb9 --- /dev/null +++ b/lambda-react-ui/.understand-anything/meta.json @@ -0,0 +1,6 @@ +{ + "lastAnalyzedAt": "2026-05-04T14:22:02.090131+00:00", + "gitCommitHash": "e5054096efec8165e7d4cfe11fc103f77ad2d98f", + "version": "1.0.0", + "analyzedFiles": 56 +} \ No newline at end of file diff --git a/nest-api/.understand-anything/.understandignore b/nest-api/.understand-anything/.understandignore new file mode 100644 index 00000000..c7034f16 --- /dev/null +++ b/nest-api/.understand-anything/.understandignore @@ -0,0 +1,2 @@ +# .understandignore — patterns for files/dirs to exclude from analysis +# Built-in defaults (always excluded): node_modules/, .git/, dist/, build/, *.lock diff --git a/nest-api/.understand-anything/knowledge-graph.json b/nest-api/.understand-anything/knowledge-graph.json new file mode 100644 index 00000000..275b9d8e --- /dev/null +++ b/nest-api/.understand-anything/knowledge-graph.json @@ -0,0 +1,382 @@ +{ + "nodes": [ + { + "id": "file:src/main.ts", + "type": "file", + "name": "main.ts", + "filePath": "src/main.ts", + "summary": "Application bootstrap entry point. Creates a NestJS app with the Fastify adapter, registers the @fastify/multipart plugin (1 MB file size limit), enables CORS for localhost:3050, applies the global HttpExceptionFilter, and starts listening on port 5050 (configurable via API_PORT env var).", + "tags": ["entrypoint", "bootstrap", "nestjs", "fastify", "cors"], + "complexity": "low" + }, + { + "id": "file:src/app.module.ts", + "type": "file", + "name": "app.module.ts", + "filePath": "src/app.module.ts", + "summary": "Root NestJS module that composes the three top-level modules: DatabaseModule (global DB provider), ApplicationsModule (core business logic), and HealthModule (liveness check).", + "tags": ["module", "nestjs", "root", "composition"], + "complexity": "low" + }, + { + "id": "file:src/applications/applications.controller.ts", + "type": "file", + "name": "applications.controller.ts", + "filePath": "src/applications/applications.controller.ts", + "summary": "REST controller mounted at /applications. Exposes CRUD endpoints (GET list, POST create, GET :id, PATCH :id, DELETE :id), archive/restore lifecycle transitions (POST :id/archive, POST :id/restore), CSV import/export (POST import, GET export, GET sample-csv), paginated history (GET :id/history, POST :id/history/restore), and interview stage sub-resources (POST/PATCH/DELETE :id/interview-stages). Uses ZodValidationPipe on every mutating endpoint.", + "tags": ["controller", "nestjs", "rest", "applications", "csv", "history", "interview-stages"], + "complexity": "high" + }, + { + "id": "file:src/applications/applications.module.ts", + "type": "file", + "name": "applications.module.ts", + "filePath": "src/applications/applications.module.ts", + "summary": "NestJS module that registers the gRPC client for nest-history-api (package history.v1, proto loaded from ../proto/history/v1/history.proto, URL from HISTORY_GRPC_HOST/HISTORY_GRPC_PORT env vars) and provides ApplicationsService, CsvService, HistoryClient, and InterviewStagesService.", + "tags": ["module", "nestjs", "grpc", "applications"], + "complexity": "moderate" + }, + { + "id": "file:src/applications/applications.service.ts", + "type": "file", + "name": "applications.service.ts", + "filePath": "src/applications/applications.service.ts", + "summary": "Core application CRUD service. Implements listApplications (filtering by status, companyCategory, jobSource, skillsMatchMin, includeArchived; sorting; pagination), getApplication, createApplication (auto-sets dateApplied unless unsubmitted), updateApplication (partial updates, forces dateApplied=null when status→unsubmitted), deleteApplication (cascades history cleanup via HistoryClient), archiveApplication, and restoreApplication. Records history events via HistoryClient after each mutation.", + "tags": ["service", "nestjs", "applications", "crud", "drizzle", "pagination", "filtering"], + "complexity": "high" + }, + { + "id": "file:src/applications/csv.service.ts", + "type": "file", + "name": "csv.service.ts", + "filePath": "src/applications/csv.service.ts", + "summary": "CSV import and export service using PapaParse. importFromCsv parses an uploaded buffer, validates each row against CsvRowSchema, deduplicates by jobPostingUrl (against DB and within-batch), and inserts valid rows. exportToCsv selects all applications ordered by dateApplied NULLS LAST and serializes to CSV. getSampleCsv returns a hardcoded header+example row template.", + "tags": ["service", "nestjs", "csv", "import", "export", "papaparse"], + "complexity": "moderate" + }, + { + "id": "file:src/applications/history.client.ts", + "type": "file", + "name": "history.client.ts", + "filePath": "src/applications/history.client.ts", + "summary": "gRPC client wrapper for nest-history-api. Implements the same public interface as the former local HistoryService. recordHistory captures a local application snapshot and sends it to the gRPC service. listHistory retrieves paginated entries and computes field-level diffs locally by comparing consecutive snapshots. restoreToVersion fetches a snapshot, applies it inside a DB transaction (replacing application + interview stages), then records a new history entry. deleteHistory proxies deletion to gRPC. Exports pure helper functions computeFieldDiffs and buildDescription.", + "tags": ["service", "grpc", "history", "nestjs", "snapshot", "diff"], + "complexity": "high" + }, + { + "id": "file:src/applications/interview-stages.service.ts", + "type": "file", + "name": "interview-stages.service.ts", + "filePath": "src/applications/interview-stages.service.ts", + "summary": "CRUD service for interview stages nested under an application. createInterviewStage verifies the parent application exists, inserts the stage, bumps application.updatedAt, and records history. updateInterviewStage applies partial updates, bumps updatedAt, records history. deleteInterviewStage pre-fetches the stage name for the history message, then deletes and bumps updatedAt.", + "tags": ["service", "nestjs", "interview-stages", "crud", "drizzle"], + "complexity": "moderate" + }, + { + "id": "file:src/applications/shared.ts", + "type": "file", + "name": "shared.ts", + "filePath": "src/applications/shared.ts", + "summary": "Pure utility functions shared across application services. formatDate converts Date objects or strings to YYYY-MM-DD strings (returns null for null input). formatDateTime serializes a Date to ISO 8601. toApplicationResponse transforms a raw Drizzle Application + InterviewStage[] into a typed ApplicationResponse, sorting stages by order.", + "tags": ["utility", "shared", "transform", "date", "response-mapping"], + "complexity": "low" + }, + { + "id": "file:src/database/database.module.ts", + "type": "file", + "name": "database.module.ts", + "filePath": "src/database/database.module.ts", + "summary": "Global NestJS module that provides and exports the Drizzle database instance (symbol DRIZZLE). Marked @Global() so any module can inject the DB without explicitly importing DatabaseModule.", + "tags": ["module", "nestjs", "database", "global", "drizzle"], + "complexity": "low" + }, + { + "id": "file:src/database/database.provider.ts", + "type": "file", + "name": "database.provider.ts", + "filePath": "src/database/database.provider.ts", + "summary": "Drizzle ORM provider. Defines the DRIZZLE injection token (Symbol) and DrizzleDB type alias. The factory reads DATABASE_URL from the environment, creates a postgres-js client, and returns a drizzle instance with the full schema (enabling relational query API).", + "tags": ["provider", "nestjs", "drizzle", "postgres", "database", "injection-token"], + "complexity": "low" + }, + { + "id": "file:src/database/schema.ts", + "type": "file", + "name": "schema.ts", + "filePath": "src/database/schema.ts", + "summary": "Drizzle ORM schema for the react_nestjs PostgreSQL schema. Defines three enums (application_status, company_category, job_source), two tables (applications with 20 columns, interview_stages with 8 columns including FK to applications with CASCADE delete), bidirectional relations, and TypeScript type exports.", + "tags": ["schema", "drizzle", "postgresql", "database", "orm", "types"], + "complexity": "moderate" + }, + { + "id": "file:src/filters/http-exception.filter.ts", + "type": "file", + "name": "http-exception.filter.ts", + "filePath": "src/filters/http-exception.filter.ts", + "summary": "Global NestJS exception filter. Handles HttpException (preserving structured { code, message } responses or mapping status codes to error codes), ZodError (returns 400 with validation_error + details array), and uncaught errors (returns 500 internal_error). Compatible with Fastify reply API.", + "tags": ["filter", "nestjs", "error-handling", "exception", "zod", "fastify"], + "complexity": "moderate" + }, + { + "id": "file:src/generated/history/v1/history.ts", + "type": "file", + "name": "history.ts", + "filePath": "src/generated/history/v1/history.ts", + "summary": "Auto-generated protobuf TypeScript bindings for history.v1 package (generated by protoc-gen-ts_proto). Defines message interfaces and encode/decode functions for RecordHistoryRequest/Response, ListHistoryRequest/Response, HistoryEntry, GetSnapshotAtVersionRequest/Response, DeleteHistoryRequest/Response, plus the HistoryServiceClient and HistoryServiceController interfaces used by the NestJS gRPC integration.", + "tags": ["generated", "protobuf", "grpc", "history", "types"], + "complexity": "high" + }, + { + "id": "file:src/health/health.controller.ts", + "type": "file", + "name": "health.controller.ts", + "filePath": "src/health/health.controller.ts", + "summary": "Simple health check controller mounted at /health. GET /health returns { status: 'ok', timestamp: ISO-string }. Used by load balancers and orchestration systems to verify API liveness.", + "tags": ["controller", "nestjs", "health", "liveness"], + "complexity": "low" + }, + { + "id": "file:src/health/health.module.ts", + "type": "file", + "name": "health.module.ts", + "filePath": "src/health/health.module.ts", + "summary": "NestJS module that registers the HealthController. Intentionally minimal — no services or exports needed.", + "tags": ["module", "nestjs", "health"], + "complexity": "low" + }, + { + "id": "file:src/pipes/zod-validation.pipe.ts", + "type": "file", + "name": "zod-validation.pipe.ts", + "filePath": "src/pipes/zod-validation.pipe.ts", + "summary": "Generic NestJS PipeTransform that accepts any ZodType schema. On parse failure, converts ZodError issues into a structured BadRequestException with code='validation_error' and a details array of { field, message } objects. Used on both query params and request bodies throughout the controller.", + "tags": ["pipe", "nestjs", "validation", "zod"], + "complexity": "low" + }, + { + "id": "file:src/types/api.ts", + "type": "file", + "name": "api.ts", + "filePath": "src/types/api.ts", + "summary": "Central Zod schema and TypeScript type definitions for the public REST API. Defines: enum schemas (ApplicationStatus, CompanyCategory, JobSource), InterviewStageSchema + CRUD inputs, ApplicationSchema + CreateApplicationSchema + UpdateApplicationSchema, ListApplicationsQuerySchema (with filtering, sorting, pagination), PaginatedApplicationsSchema, HistoryEntry/PaginatedHistorySchema, RestoreRequestSchema, CsvRowSchema (with boolean coercion for coverLetterRequired/isArchived), and the ImportResult interface.", + "tags": ["types", "zod", "api", "validation", "schema", "enums"], + "complexity": "high" + }, + { + "id": "file:drizzle.config.ts", + "type": "config", + "name": "drizzle.config.ts", + "filePath": "drizzle.config.ts", + "summary": "Drizzle Kit configuration file. Points to src/database/schema.ts as the schema source, outputs migrations to src/database/migrations, uses postgresql dialect, reads DATABASE_URL from env, and scopes operations to the react_nestjs schema.", + "tags": ["config", "drizzle", "migrations", "database"], + "complexity": "low" + }, + { + "id": "file:tsconfig.json", + "type": "config", + "name": "tsconfig.json", + "filePath": "tsconfig.json", + "summary": "TypeScript compiler configuration. Targets ES2022, uses NodeNext module resolution, enables strict mode, emitDecoratorMetadata and experimentalDecorators (required for NestJS DI), and outputs to dist/.", + "tags": ["config", "typescript", "compiler"], + "complexity": "low" + }, + { + "id": "file:package.json", + "type": "config", + "name": "package.json", + "filePath": "package.json", + "summary": "Package manifest for nest-api. Key dependencies: NestJS 11 (platform-fastify, microservices, common, core), Drizzle ORM 0.45, postgres-js 3.4, Zod 4, PapaParse 5.5, @bufbuild/protobuf + @grpc/grpc-js for gRPC. Dev: tsx for hot reload, drizzle-kit for migrations, vitest 4 for tests. npm overrides pin path-to-regexp, picomatch, vite, and fastify to secure/compatible versions.", + "tags": ["config", "package", "dependencies", "npm"], + "complexity": "low" + } + ], + "edges": [ + { "source": "file:src/main.ts", "target": "file:src/app.module.ts", "type": "imports", "label": "imports AppModule" }, + { "source": "file:src/main.ts", "target": "file:src/filters/http-exception.filter.ts", "type": "imports", "label": "uses HttpExceptionFilter globally" }, + { "source": "file:src/app.module.ts", "target": "file:src/database/database.module.ts", "type": "imports", "label": "imports DatabaseModule" }, + { "source": "file:src/app.module.ts", "target": "file:src/applications/applications.module.ts", "type": "imports", "label": "imports ApplicationsModule" }, + { "source": "file:src/app.module.ts", "target": "file:src/health/health.module.ts", "type": "imports", "label": "imports HealthModule" }, + { "source": "file:src/applications/applications.module.ts", "target": "file:src/applications/applications.controller.ts", "type": "imports", "label": "registers controller" }, + { "source": "file:src/applications/applications.module.ts", "target": "file:src/applications/applications.service.ts", "type": "imports", "label": "provides service" }, + { "source": "file:src/applications/applications.module.ts", "target": "file:src/applications/csv.service.ts", "type": "imports", "label": "provides CsvService" }, + { "source": "file:src/applications/applications.module.ts", "target": "file:src/applications/history.client.ts", "type": "imports", "label": "provides HistoryClient" }, + { "source": "file:src/applications/applications.module.ts", "target": "file:src/applications/interview-stages.service.ts", "type": "imports", "label": "provides InterviewStagesService" }, + { "source": "file:src/applications/applications.controller.ts", "target": "file:src/applications/applications.service.ts", "type": "depends-on", "label": "injects ApplicationsService" }, + { "source": "file:src/applications/applications.controller.ts", "target": "file:src/applications/csv.service.ts", "type": "depends-on", "label": "injects CsvService" }, + { "source": "file:src/applications/applications.controller.ts", "target": "file:src/applications/history.client.ts", "type": "depends-on", "label": "injects HistoryClient" }, + { "source": "file:src/applications/applications.controller.ts", "target": "file:src/applications/interview-stages.service.ts", "type": "depends-on", "label": "injects InterviewStagesService" }, + { "source": "file:src/applications/applications.controller.ts", "target": "file:src/pipes/zod-validation.pipe.ts", "type": "depends-on", "label": "uses ZodValidationPipe" }, + { "source": "file:src/applications/applications.controller.ts", "target": "file:src/types/api.ts", "type": "imports", "label": "uses API schemas and types" }, + { "source": "file:src/applications/applications.service.ts", "target": "file:src/database/database.provider.ts", "type": "depends-on", "label": "injects DRIZZLE token" }, + { "source": "file:src/applications/applications.service.ts", "target": "file:src/database/schema.ts", "type": "imports", "label": "uses applications and interviewStages tables" }, + { "source": "file:src/applications/applications.service.ts", "target": "file:src/applications/shared.ts", "type": "imports", "label": "uses toApplicationResponse" }, + { "source": "file:src/applications/applications.service.ts", "target": "file:src/applications/history.client.ts", "type": "depends-on", "label": "records history events" }, + { "source": "file:src/applications/applications.service.ts", "target": "file:src/types/api.ts", "type": "imports", "label": "uses input/response types" }, + { "source": "file:src/applications/csv.service.ts", "target": "file:src/database/database.provider.ts", "type": "depends-on", "label": "injects DRIZZLE token" }, + { "source": "file:src/applications/csv.service.ts", "target": "file:src/database/schema.ts", "type": "imports", "label": "uses applications table" }, + { "source": "file:src/applications/csv.service.ts", "target": "file:src/applications/history.client.ts", "type": "depends-on", "label": "records import history" }, + { "source": "file:src/applications/csv.service.ts", "target": "file:src/types/api.ts", "type": "imports", "label": "uses CsvRowSchema and ImportResult" }, + { "source": "file:src/applications/history.client.ts", "target": "file:src/database/database.provider.ts", "type": "depends-on", "label": "injects DRIZZLE token for snapshots" }, + { "source": "file:src/applications/history.client.ts", "target": "file:src/database/schema.ts", "type": "imports", "label": "reads applications and interviewStages" }, + { "source": "file:src/applications/history.client.ts", "target": "file:src/applications/shared.ts", "type": "imports", "label": "uses toApplicationResponse for snapshots" }, + { "source": "file:src/applications/history.client.ts", "target": "file:src/generated/history/v1/history.ts", "type": "imports", "label": "uses HistoryServiceClient interface" }, + { "source": "file:src/applications/history.client.ts", "target": "file:src/types/api.ts", "type": "imports", "label": "uses ApplicationResponse and HistoryEntry types" }, + { "source": "file:src/applications/interview-stages.service.ts", "target": "file:src/database/database.provider.ts", "type": "depends-on", "label": "injects DRIZZLE token" }, + { "source": "file:src/applications/interview-stages.service.ts", "target": "file:src/database/schema.ts", "type": "imports", "label": "uses applications and interviewStages tables" }, + { "source": "file:src/applications/interview-stages.service.ts", "target": "file:src/applications/shared.ts", "type": "imports", "label": "uses formatDate" }, + { "source": "file:src/applications/interview-stages.service.ts", "target": "file:src/applications/history.client.ts", "type": "depends-on", "label": "records stage change history" }, + { "source": "file:src/applications/interview-stages.service.ts", "target": "file:src/types/api.ts", "type": "imports", "label": "uses stage input/response types" }, + { "source": "file:src/applications/shared.ts", "target": "file:src/database/schema.ts", "type": "imports", "label": "uses Application and InterviewStage types" }, + { "source": "file:src/applications/shared.ts", "target": "file:src/types/api.ts", "type": "imports", "label": "uses ApplicationResponse type" }, + { "source": "file:src/database/database.module.ts", "target": "file:src/database/database.provider.ts", "type": "imports", "label": "provides and exports drizzleProvider" }, + { "source": "file:src/database/database.provider.ts", "target": "file:src/database/schema.ts", "type": "imports", "label": "passes schema to drizzle for relational API" }, + { "source": "file:src/health/health.module.ts", "target": "file:src/health/health.controller.ts", "type": "imports", "label": "registers HealthController" }, + { "source": "file:src/pipes/zod-validation.pipe.ts", "target": "file:src/types/api.ts", "type": "depends-on", "label": "validates against API schemas" }, + { "source": "file:drizzle.config.ts", "target": "file:src/database/schema.ts", "type": "depends-on", "label": "references schema for migrations" }, + { "source": "file:tsconfig.json", "target": "file:src/main.ts", "type": "depends-on", "label": "compiles all src/ TypeScript files" }, + { "source": "file:package.json", "target": "file:src/main.ts", "type": "depends-on", "label": "dev/build/start scripts target src/main.ts" } + ], + "layers": [ + { + "id": "layer:entry", + "name": "Application Bootstrap", + "description": "Entry point and root module composition. Sets up the Fastify HTTP adapter, global middleware (CORS, multipart, exception filter), and wires together the three top-level NestJS modules.", + "nodeIds": ["file:src/main.ts", "file:src/app.module.ts"] + }, + { + "id": "layer:http", + "name": "HTTP Controllers & Pipes", + "description": "HTTP-facing layer. The ApplicationsController maps all REST routes under /applications. HealthController provides a liveness endpoint. ZodValidationPipe performs schema validation at the boundary before requests reach services.", + "nodeIds": [ + "file:src/applications/applications.controller.ts", + "file:src/health/health.controller.ts", + "file:src/pipes/zod-validation.pipe.ts", + "file:src/filters/http-exception.filter.ts" + ] + }, + { + "id": "layer:modules", + "name": "NestJS Module Definitions", + "description": "Module wiring layer. Each module declares its providers, controllers, and imports. ApplicationsModule additionally registers the gRPC client for the external nest-history-api service.", + "nodeIds": [ + "file:src/applications/applications.module.ts", + "file:src/health/health.module.ts", + "file:src/database/database.module.ts" + ] + }, + { + "id": "layer:services", + "name": "Business Logic Services", + "description": "Core domain services that implement application tracker business rules: CRUD with filtering/sorting/pagination, CSV import with deduplication and validation, versioned history via gRPC, and interview stage management with parent-application coupling.", + "nodeIds": [ + "file:src/applications/applications.service.ts", + "file:src/applications/csv.service.ts", + "file:src/applications/history.client.ts", + "file:src/applications/interview-stages.service.ts", + "file:src/applications/shared.ts" + ] + }, + { + "id": "layer:types", + "name": "API Types & Validation Schemas", + "description": "Centralised Zod schemas and TypeScript types for the public API surface. Acts as the contract between HTTP layer and service layer — every input and output shape is defined here.", + "nodeIds": ["file:src/types/api.ts"] + }, + { + "id": "layer:database", + "name": "Database (Drizzle ORM)", + "description": "PostgreSQL persistence layer using Drizzle ORM under the react_nestjs schema. Includes the global DB provider (injection token, connection factory), the full relational schema with enums and relations, and the Drizzle Kit migration config.", + "nodeIds": [ + "file:src/database/database.provider.ts", + "file:src/database/schema.ts", + "file:drizzle.config.ts" + ] + }, + { + "id": "layer:external", + "name": "External Integration (gRPC / Protobuf)", + "description": "Generated TypeScript protobuf bindings for the history.v1 gRPC service. Used exclusively by HistoryClient to communicate with the separate nest-history-api microservice over gRPC.", + "nodeIds": ["file:src/generated/history/v1/history.ts"] + }, + { + "id": "layer:config", + "name": "Project Configuration", + "description": "Build-time configuration files: TypeScript compiler options, npm package manifest with dependency pins and security overrides.", + "nodeIds": ["file:tsconfig.json", "file:package.json"] + } + ], + "tour": [ + { + "order": 1, + "title": "Start Here: Application Bootstrap", + "description": "The server starts in src/main.ts. It creates a NestJS application using the Fastify adapter (for performance), registers @fastify/multipart for CSV file uploads, enables CORS for the frontend on port 3050, applies the global exception filter, and listens on port 5050. This is the root of the dependency graph — trace every other file from here.", + "nodeIds": ["file:src/main.ts", "file:src/app.module.ts"] + }, + { + "order": 2, + "title": "Database Layer: Schema and Provider", + "description": "Before looking at any business logic, understand the data model. src/database/schema.ts defines the react_nestjs PostgreSQL schema with two tables (applications, interview_stages) and three enums. src/database/database.provider.ts wires up a Drizzle ORM client injected via the DRIZZLE symbol — all services receive this. src/database/database.module.ts marks the provider as @Global() so any module can inject it.", + "nodeIds": [ + "file:src/database/schema.ts", + "file:src/database/database.provider.ts", + "file:src/database/database.module.ts" + ] + }, + { + "order": 3, + "title": "API Contract: Types and Validation Schemas", + "description": "src/types/api.ts is the single source of truth for every request and response shape. It uses Zod to define create/update schemas for applications and interview stages, list query params with filtering/sorting/pagination, paginated responses, history types, the CSV row schema with boolean coercion, and the error response format. Read this before looking at the controller or services.", + "nodeIds": ["file:src/types/api.ts"] + }, + { + "order": 4, + "title": "HTTP Layer: Controller and Validation Pipe", + "description": "src/applications/applications.controller.ts maps every REST route under /applications. It delegates immediately to services — there is no business logic here. The ZodValidationPipe (src/pipes/zod-validation.pipe.ts) validates every incoming request body and query string against the Zod schemas from api.ts, throwing structured 400 errors before the service is ever called. The HttpExceptionFilter (src/filters/http-exception.filter.ts) converts all thrown exceptions into a consistent { code, message } error envelope.", + "nodeIds": [ + "file:src/applications/applications.controller.ts", + "file:src/pipes/zod-validation.pipe.ts", + "file:src/filters/http-exception.filter.ts" + ] + }, + { + "order": 5, + "title": "Core Business Logic: ApplicationsService", + "description": "src/applications/applications.service.ts contains the primary CRUD operations. Key patterns: listApplications builds Drizzle WHERE conditions dynamically from query filters and uses sql.raw for nullable date sorting; createApplication auto-sets dateApplied based on status; updateApplication forces dateApplied=null when status becomes 'unsubmitted'; every mutation calls HistoryClient.recordHistory. The shared.ts utility provides toApplicationResponse, the canonical DB-to-API response transform.", + "nodeIds": [ + "file:src/applications/applications.service.ts", + "file:src/applications/shared.ts" + ] + }, + { + "order": 6, + "title": "History: gRPC Client and Protobuf Bindings", + "description": "src/applications/history.client.ts is the most architecturally interesting file. It wraps the gRPC connection to the separate nest-history-api service. Snapshots are captured locally (reading the DB), serialised to JSON bytes, and sent over gRPC. Diffs are computed locally by comparing consecutive snapshots — the remote service stores opaque bytes. Restore-to-version uses a DB transaction to atomically replace the application and its interview stages. The protobuf message types are in src/generated/history/v1/history.ts (auto-generated; do not edit).", + "nodeIds": [ + "file:src/applications/history.client.ts", + "file:src/generated/history/v1/history.ts" + ] + }, + { + "order": 7, + "title": "Interview Stages: Nested CRUD", + "description": "src/applications/interview-stages.service.ts manages interview stages, which are always scoped to a parent application. Every mutation bumps the parent's updatedAt (important for the default sort) and records a history event. The service validates parent existence on create and uses compound WHERE (stageId AND applicationId) on update/delete to prevent cross-application access.", + "nodeIds": ["file:src/applications/interview-stages.service.ts"] + }, + { + "order": 8, + "title": "CSV Import/Export", + "description": "src/applications/csv.service.ts handles bulk data operations. Import: receives a multipart upload buffer, parses it with PapaParse (full-file parsing to handle embedded newlines), validates each row with CsvRowSchema, deduplicates by jobPostingUrl against both the DB and within the current batch, then inserts and records history for each row. Export: fetches all rows ordered by dateApplied NULLS LAST and serialises to CSV. The getSampleCsv method returns a downloadable template.", + "nodeIds": ["file:src/applications/csv.service.ts"] + }, + { + "order": 9, + "title": "Module Wiring and Health Check", + "description": "src/applications/applications.module.ts wires all application-domain providers and also configures the NestJS gRPC ClientsModule to connect to nest-history-api (host/port from env vars). src/health/health.controller.ts + health.module.ts provide a trivial GET /health liveness check used by container orchestration.", + "nodeIds": [ + "file:src/applications/applications.module.ts", + "file:src/health/health.controller.ts", + "file:src/health/health.module.ts" + ] + } + ] +} diff --git a/nest-api/.understand-anything/meta.json b/nest-api/.understand-anything/meta.json new file mode 100644 index 00000000..be637019 --- /dev/null +++ b/nest-api/.understand-anything/meta.json @@ -0,0 +1,25 @@ +{ + "version": "2.5.1", + "projectName": "nest-api", + "description": "NestJS TypeScript REST API for the application tracker. Uses Drizzle ORM with PostgreSQL (react_nestjs schema), Fastify adapter, Zod validation, CSV import/export, and gRPC communication with nest-history-api for versioned application history. Serves tanstack-ui and react-ui frontends on port 5050.", + "generatedAt": "2026-05-04T00:00:00.000Z", + "gitHash": "e5054096efec8165e7d4cfe11fc103f77ad2d98f", + "languages": [ + "TypeScript" + ], + "frameworks": [ + "NestJS", + "Drizzle ORM", + "Fastify", + "Zod", + "gRPC" + ], + "entryPoint": "src/main.ts", + "stats": { + "filesAnalyzed": 21, + "totalNodes": 21, + "totalEdges": 44, + "totalLayers": 8, + "tourSteps": 9 + } +} diff --git a/nest-history-api/.understand-anything/.understandignore b/nest-history-api/.understand-anything/.understandignore new file mode 100644 index 00000000..c7034f16 --- /dev/null +++ b/nest-history-api/.understand-anything/.understandignore @@ -0,0 +1,2 @@ +# .understandignore — patterns for files/dirs to exclude from analysis +# Built-in defaults (always excluded): node_modules/, .git/, dist/, build/, *.lock diff --git a/nest-history-api/.understand-anything/knowledge-graph.json b/nest-history-api/.understand-anything/knowledge-graph.json new file mode 100644 index 00000000..c901a5cb --- /dev/null +++ b/nest-history-api/.understand-anything/knowledge-graph.json @@ -0,0 +1,395 @@ +{ + "nodes": [ + { + "id": "file:src/main.ts", + "type": "file", + "name": "main.ts", + "filePath": "src/main.ts", + "summary": "Application entry point. Bootstraps NestJS as a pure gRPC microservice using NestFactory.createMicroservice with Transport.GRPC. Reads HISTORY_GRPC_PORT (default 50051) from env. Loads the history.proto from the repo-level proto/ directory relative to process.cwd().", + "tags": ["entry-point", "nestjs", "grpc", "microservice", "bootstrap"], + "complexity": "moderate" + }, + { + "id": "file:src/app.module.ts", + "type": "file", + "name": "app.module.ts", + "filePath": "src/app.module.ts", + "summary": "Root NestJS module. Imports DatabaseModule (global Knex provider) and HistoryModule (controller + service). Minimal composition root — delegates all logic to feature modules.", + "tags": ["nestjs", "module", "root", "composition"], + "complexity": "low" + }, + { + "id": "file:src/database/database.module.ts", + "type": "file", + "name": "database.module.ts", + "filePath": "src/database/database.module.ts", + "summary": "Global NestJS module that provides the Knex instance via the KNEX symbol token. Reads DATABASE_URL from environment via dotenv. Configures pg client with react_nestjs_history search path and connection pool (min 1, max 10). Exported globally so all modules can inject KNEX without re-importing.", + "tags": ["nestjs", "module", "database", "knex", "postgresql", "global", "dependency-injection"], + "complexity": "moderate" + }, + { + "id": "file:src/history/history.module.ts", + "type": "file", + "name": "history.module.ts", + "filePath": "src/history/history.module.ts", + "summary": "NestJS feature module wiring HistoryController and HistoryService together. Declares the controller and registers the service as a provider.", + "tags": ["nestjs", "module", "history", "feature-module"], + "complexity": "low" + }, + { + "id": "file:src/history/history.controller.ts", + "type": "file", + "name": "history.controller.ts", + "filePath": "src/history/history.controller.ts", + "summary": "NestJS gRPC controller implementing the HistoryService proto contract. Exposes four @GrpcMethod handlers: RecordHistory (write snapshot), ListHistory (paginated listing), GetSnapshotAtVersion (point-in-time retrieval), DeleteHistory (bulk delete). Delegates business logic to HistoryService. Converts Buffer/Uint8Array for proto transport.", + "tags": ["nestjs", "grpc", "controller", "history", "rpc", "protobuf"], + "complexity": "moderate" + }, + { + "id": "file:src/history/history.service.ts", + "type": "file", + "name": "history.service.ts", + "filePath": "src/history/history.service.ts", + "summary": "Injectable NestJS service handling all database operations for history. Uses Knex with react_nestjs_history schema. Implements recordHistory (transactional insert with auto-increment sequence), listHistory (paginated query with count), getSnapshotAtVersion (point lookup), and deleteHistory (bulk delete by applicationId). Delegates pure logic to history-logic.ts helpers.", + "tags": ["nestjs", "service", "history", "database", "knex", "postgresql", "transactions"], + "complexity": "high" + }, + { + "id": "file:src/history/history-logic.ts", + "type": "file", + "name": "history-logic.ts", + "filePath": "src/history/history-logic.ts", + "summary": "Pure helper functions extracted from HistoryService for unit-testability without DB or mocks. Provides: parseSnapshot (Buffer→JSON, throws RpcException on invalid JSON), clampPagination (normalize page/limit/offset), toDate (Date|string→Date), encodeSnapshot (unknown→Buffer), countFromQuery (pg count result coercion to number).", + "tags": ["pure-functions", "helpers", "history", "pagination", "snapshot", "testable"], + "complexity": "moderate" + }, + { + "id": "file:src/history/history.service.spec.ts", + "type": "file", + "name": "history.service.spec.ts", + "filePath": "src/history/history.service.spec.ts", + "summary": "Vitest unit tests for all pure helper functions in history-logic.ts. Tests parseSnapshot (valid JSON, scalars, invalid bytes, empty buffer), clampPagination (positive values, zero/negative page, zero/negative limit, offset math), toDate (Date passthrough, ISO string parsing), encodeSnapshot (roundtrip), and countFromQuery (undefined, string count, numeric count). No DB or Nest module required.", + "tags": ["test", "unit-test", "vitest", "pure-functions", "history-logic"], + "complexity": "moderate" + }, + { + "id": "file:src/generated/history/v1/history.ts", + "type": "file", + "name": "history.ts", + "filePath": "src/generated/history/v1/history.ts", + "summary": "Generated TypeScript file from history.proto via protoc-gen-ts_proto. Defines protobuf message types (RecordHistoryRequest/Response, ListHistoryRequest, HistoryEntry, ListHistoryResponse, GetSnapshotAtVersionRequest/Response, DeleteHistoryRequest/Response), encode/decode methods, HistoryServiceClient/Controller/Server interfaces, and serialization helpers. DO NOT edit manually.", + "tags": ["generated", "protobuf", "grpc", "types", "history", "ts-proto"], + "complexity": "high" + }, + { + "id": "file:migrations/20260413000001_create_application_history.ts", + "type": "file", + "name": "20260413000001_create_application_history.ts", + "filePath": "migrations/20260413000001_create_application_history.ts", + "summary": "Knex migration that creates the react_nestjs_history schema and application_history table. Table has UUID primary key (gen_random_uuid()), application_id (UUID, not null), sequence (integer), description (text), snapshot (jsonb), created_at (timestamptz). Unique constraint on (application_id, sequence) for monotonic sequence enforcement.", + "tags": ["migration", "knex", "database", "schema", "postgresql", "application_history"], + "complexity": "moderate" + }, + { + "id": "file:migrations/20260413000002_copy_existing_history.ts", + "type": "file", + "name": "20260413000002_copy_existing_history.ts", + "filePath": "migrations/20260413000002_copy_existing_history.ts", + "summary": "Knex migration that copies existing history rows from react_nestjs schema to the new react_nestjs_history schema using INSERT ... SELECT ... ON CONFLICT DO NOTHING. Idempotent. Checks for source table existence before copying. Down is a no-op by design.", + "tags": ["migration", "knex", "data-migration", "idempotent", "schema-migration"], + "complexity": "moderate" + }, + { + "id": "file:knexfile.ts", + "type": "config", + "name": "knexfile.ts", + "filePath": "knexfile.ts", + "summary": "Knex configuration file for running migrations. Uses pg client, reads DATABASE_URL from env, sets searchPath to react_nestjs_history and public. Configures migrations directory to ./migrations with .ts extension loaded via tsx.", + "tags": ["config", "knex", "database", "migrations", "postgresql"], + "complexity": "low" + }, + { + "id": "file:tsconfig.json", + "type": "config", + "name": "tsconfig.json", + "filePath": "tsconfig.json", + "summary": "TypeScript configuration. Target ES2022, module CommonJS, strict mode, experimentalDecorators and emitDecoratorMetadata enabled for NestJS DI. Outputs to dist/, includes only src/. Skip lib check enabled.", + "tags": ["config", "typescript", "build"], + "complexity": "low" + }, + { + "id": "file:eslint.config.mjs", + "type": "config", + "name": "eslint.config.mjs", + "filePath": "eslint.config.mjs", + "summary": "ESLint flat config using typescript-eslint recommended rules. Ignores dist/, node_modules/, and src/generated/ (generated protobuf code). Uses .mjs extension because package is CJS type — ESLint config must be ESM.", + "tags": ["config", "eslint", "linting"], + "complexity": "low" + }, + { + "id": "file:vitest.config.ts", + "type": "config", + "name": "vitest.config.ts", + "filePath": "vitest.config.ts", + "summary": "Vitest configuration for unit tests. Includes src/**/*.spec.ts, excludes node_modules and dist. Node environment (no DOM). Runs pure function tests without any DB setup.", + "tags": ["config", "vitest", "testing"], + "complexity": "low" + }, + { + "id": "file:package.json", + "type": "config", + "name": "package.json", + "filePath": "package.json", + "summary": "Package manifest for nest-history-api. CJS type. Key deps: NestJS 11 (common, core, microservices), @grpc/grpc-js, @bufbuild/protobuf, knex, pg, rxjs, reflect-metadata, dotenv. Dev: typescript-eslint, tsx (for dev+migration), vitest. Scripts: dev (tsx watch), build (tsc), start, lint, test, migrate (knex migrate:latest).", + "tags": ["config", "package-manifest", "dependencies"], + "complexity": "low" + }, + { + "id": "file:CLAUDE.md", + "type": "document", + "name": "CLAUDE.md", + "filePath": "CLAUDE.md", + "summary": "Stack-specific AI agent guidance. Documents gRPC/protobuf patterns: buf CLI usage, proto naming conventions, opaque snapshot bytes design decision, pure microservice bootstrap, protoPath resolution, gRPC client registration in consumers, Observable→Promise wrapping, @bufbuild/protobuf as explicit dep, cross-schema cascade delete strategy, CJS vs ESM choices, sandbox quirks, and test split rationale.", + "tags": ["documentation", "ai-guidance", "grpc", "patterns", "architecture"], + "complexity": "moderate" + }, + { + "id": "file:.auditconfig.json", + "type": "config", + "name": ".auditconfig.json", + "filePath": ".auditconfig.json", + "summary": "audit-ci configuration. No allowlisted advisories. Reports moderate, high, and critical vulnerabilities. Clean baseline with no exceptions.", + "tags": ["config", "security", "audit"], + "complexity": "low" + }, + { + "id": "file:.env.example", + "type": "config", + "name": ".env.example", + "filePath": ".env.example", + "summary": "Example environment variable template. Documents DATABASE_URL and HISTORY_GRPC_PORT required for running the service.", + "tags": ["config", "environment", "documentation"], + "complexity": "low" + } + ], + "edges": [ + { + "source": "file:src/main.ts", + "target": "file:src/app.module.ts", + "type": "imports", + "label": "imports AppModule" + }, + { + "source": "file:src/app.module.ts", + "target": "file:src/database/database.module.ts", + "type": "imports", + "label": "imports DatabaseModule" + }, + { + "source": "file:src/app.module.ts", + "target": "file:src/history/history.module.ts", + "type": "imports", + "label": "imports HistoryModule" + }, + { + "source": "file:src/history/history.module.ts", + "target": "file:src/history/history.controller.ts", + "type": "imports", + "label": "declares HistoryController" + }, + { + "source": "file:src/history/history.module.ts", + "target": "file:src/history/history.service.ts", + "type": "imports", + "label": "provides HistoryService" + }, + { + "source": "file:src/history/history.controller.ts", + "target": "file:src/history/history.service.ts", + "type": "imports", + "label": "injects HistoryService" + }, + { + "source": "file:src/history/history.controller.ts", + "target": "file:src/generated/history/v1/history.ts", + "type": "imports", + "label": "imports proto message types" + }, + { + "source": "file:src/history/history.service.ts", + "target": "file:src/database/database.module.ts", + "type": "imports", + "label": "injects KNEX token" + }, + { + "source": "file:src/history/history.service.ts", + "target": "file:src/history/history-logic.ts", + "type": "imports", + "label": "uses pure helper functions" + }, + { + "source": "file:src/history/history.service.spec.ts", + "target": "file:src/history/history-logic.ts", + "type": "imports", + "label": "tests pure helpers" + }, + { + "source": "file:knexfile.ts", + "target": "file:migrations/20260413000001_create_application_history.ts", + "type": "references", + "label": "migration directory" + }, + { + "source": "file:knexfile.ts", + "target": "file:migrations/20260413000002_copy_existing_history.ts", + "type": "references", + "label": "migration directory" + }, + { + "source": "file:src/main.ts", + "target": "file:knexfile.ts", + "type": "references", + "label": "shares DATABASE_URL env var" + }, + { + "source": "file:vitest.config.ts", + "target": "file:src/history/history.service.spec.ts", + "type": "references", + "label": "test discovery pattern" + }, + { + "source": "file:eslint.config.mjs", + "target": "file:src/generated/history/v1/history.ts", + "type": "references", + "label": "ignores generated directory" + } + ], + "layers": [ + { + "id": "layer:bootstrap", + "name": "Bootstrap & Configuration", + "description": "Application entry point and all configuration files. main.ts wires up NestJS as a pure gRPC microservice; config files govern TypeScript compilation, linting, testing, and security auditing.", + "nodeIds": [ + "file:src/main.ts", + "file:package.json", + "file:tsconfig.json", + "file:eslint.config.mjs", + "file:vitest.config.ts", + "file:.auditconfig.json", + "file:.env.example" + ] + }, + { + "id": "layer:modules", + "name": "NestJS Module Composition", + "description": "NestJS module hierarchy: AppModule (root) imports DatabaseModule (global Knex provider) and HistoryModule (feature). Modules wire providers, controllers, and exports together using NestJS DI.", + "nodeIds": [ + "file:src/app.module.ts", + "file:src/database/database.module.ts", + "file:src/history/history.module.ts" + ] + }, + { + "id": "layer:grpc-transport", + "name": "gRPC Transport Layer", + "description": "The gRPC controller and generated protobuf types. HistoryController exposes four @GrpcMethod handlers that map proto RPCs to service calls. The generated history.ts file provides message types and serialization derived from history.proto.", + "nodeIds": [ + "file:src/history/history.controller.ts", + "file:src/generated/history/v1/history.ts" + ] + }, + { + "id": "layer:business-logic", + "name": "Business Logic & Database", + "description": "Core domain logic. HistoryService performs all Knex queries against the react_nestjs_history schema. history-logic.ts contains pure, testable helper functions (snapshot parsing, pagination clamping, type coercion) extracted from the service to enable unit testing without a database.", + "nodeIds": [ + "file:src/history/history.service.ts", + "file:src/history/history-logic.ts" + ] + }, + { + "id": "layer:database", + "name": "Database Migrations", + "description": "Knex migration files that manage the react_nestjs_history PostgreSQL schema. Migration 1 creates the schema and application_history table. Migration 2 copies existing history rows from the old react_nestjs schema (idempotent, ON CONFLICT DO NOTHING). knexfile.ts configures the Knex CLI for running migrations.", + "nodeIds": [ + "file:migrations/20260413000001_create_application_history.ts", + "file:migrations/20260413000002_copy_existing_history.ts", + "file:knexfile.ts" + ] + }, + { + "id": "layer:testing", + "name": "Tests", + "description": "Unit tests for pure helper functions in history-logic.ts using Vitest. No database or NestJS module setup required — tests run fast and in isolation.", + "nodeIds": [ + "file:src/history/history.service.spec.ts" + ] + }, + { + "id": "layer:documentation", + "name": "Documentation", + "description": "Project documentation and AI agent guidance. CLAUDE.md documents gRPC/protobuf patterns, design decisions (opaque snapshot bytes, CJS vs ESM), and operational gotchas specific to this microservice.", + "nodeIds": [ + "file:CLAUDE.md" + ] + } + ], + "tour": [ + { + "id": "tour:step-1", + "order": 1, + "title": "Service Entry Point", + "description": "Start at main.ts to understand how this microservice bootstraps. Unlike typical NestJS apps, there is no HTTP listener — NestFactory.createMicroservice creates a pure gRPC server on port 50051. The proto file is loaded from the repo-level proto/ directory. This pure-microservice pattern is the defining architectural choice of the entire service.", + "nodeIds": ["file:src/main.ts"] + }, + { + "id": "tour:step-2", + "order": 2, + "title": "Proto Contract (Generated Types)", + "description": "Before exploring the implementation, read the generated TypeScript in src/generated/history/v1/history.ts. This file defines the four gRPC RPCs: RecordHistory (write a snapshot), ListHistory (paginated retrieval), GetSnapshotAtVersion (point-in-time lookup), and DeleteHistory (cleanup on application deletion). All message types, serialization, and the HistoryServiceClient/Controller interfaces derive from here. Note that snapshots are opaque bytes — the history service never interprets application fields.", + "nodeIds": ["file:src/generated/history/v1/history.ts"] + }, + { + "id": "tour:step-3", + "order": 3, + "title": "Module Wiring", + "description": "AppModule composes DatabaseModule and HistoryModule. DatabaseModule is @Global(), providing the Knex instance under the KNEX symbol token to every module without requiring explicit re-import. HistoryModule wires the controller and service together as a standard NestJS feature module.", + "nodeIds": ["file:src/app.module.ts", "file:src/database/database.module.ts", "file:src/history/history.module.ts"] + }, + { + "id": "tour:step-4", + "order": 4, + "title": "gRPC Controller", + "description": "HistoryController maps each gRPC method to a @GrpcMethod decorated handler. It handles Buffer/Uint8Array conversions needed for proto transport and delegates all persistence to HistoryService. The controller is thin — no business logic lives here.", + "nodeIds": ["file:src/history/history.controller.ts"] + }, + { + "id": "tour:step-5", + "order": 5, + "title": "Pure Business Logic Helpers", + "description": "history-logic.ts contains pure helper functions extracted from HistoryService: parseSnapshot (validates and parses the opaque JSON bytes, throws RpcException on invalid JSON), clampPagination (normalizes page/limit to safe values and computes offset), toDate (handles pg returning strings vs Date objects), encodeSnapshot, and countFromQuery. These functions have zero I/O dependencies and are fully unit-testable.", + "nodeIds": ["file:src/history/history-logic.ts"] + }, + { + "id": "tour:step-6", + "order": 6, + "title": "Database Service", + "description": "HistoryService performs all Knex queries against the react_nestjs_history PostgreSQL schema. Key patterns: recordHistory uses a transaction to atomically compute the next sequence number (MAX+1) and insert; listHistory runs COUNT and SELECT in parallel for efficiency; deleteHistory provides best-effort cleanup called when an application is deleted in nest-api. All queries use .withSchema('react_nestjs_history') for schema isolation.", + "nodeIds": ["file:src/history/history.service.ts"] + }, + { + "id": "tour:step-7", + "order": 7, + "title": "Database Schema & Migrations", + "description": "Migration 1 creates the react_nestjs_history schema and application_history table (UUID PK, application_id UUID, integer sequence, text description, jsonb snapshot, timestamptz created_at) with a unique constraint on (application_id, sequence) that enforces monotonic per-application sequencing. Migration 2 copies existing rows from the old react_nestjs schema during the schema extraction — it is idempotent via ON CONFLICT DO NOTHING.", + "nodeIds": ["file:migrations/20260413000001_create_application_history.ts", "file:migrations/20260413000002_copy_existing_history.ts", "file:knexfile.ts"] + }, + { + "id": "tour:step-8", + "order": 8, + "title": "Unit Tests", + "description": "history.service.spec.ts tests all five helper functions in history-logic.ts using Vitest. Tests run without any database or NestJS module — they exercise pure logic only. This follows the codebase's test design principle: extract non-trivial logic into pure functions and unit-test those directly, reserving integration tests for real I/O.", + "nodeIds": ["file:src/history/history.service.spec.ts"] + } + ] +} diff --git a/nest-history-api/.understand-anything/meta.json b/nest-history-api/.understand-anything/meta.json new file mode 100644 index 00000000..f9a292f0 --- /dev/null +++ b/nest-history-api/.understand-anything/meta.json @@ -0,0 +1,34 @@ +{ + "version": "2.5.1", + "projectName": "nest-history-api", + "description": "NestJS gRPC microservice for application history tracking. Communicates via gRPC with nest-api. Uses Knex for PostgreSQL migrations and raw queries. Stores application snapshots as opaque JSON bytes in the react_nestjs_history schema.", + "gitHash": "e5054096efec8165e7d4cfe11fc103f77ad2d98f", + "generatedAt": "2026-05-04T00:00:00.000Z", + "languages": [ + "TypeScript" + ], + "frameworks": [ + "NestJS", + "gRPC", + "Knex", + "Protocol Buffers", + "Vitest" + ], + "entryPoint": "src/main.ts", + "stats": { + "totalFiles": 19, + "totalNodes": 19, + "totalEdges": 15, + "totalLayers": 7, + "tourSteps": 8, + "nodeTypes": { + "file": 11, + "config": 7, + "document": 1 + }, + "edgeTypes": { + "imports": 10, + "references": 5 + } + } +} diff --git a/nuxt-api/.understand-anything/.understandignore b/nuxt-api/.understand-anything/.understandignore new file mode 100644 index 00000000..a764245a --- /dev/null +++ b/nuxt-api/.understand-anything/.understandignore @@ -0,0 +1,6 @@ +# .understandignore — patterns for files/dirs to exclude from analysis +# Built-in defaults (always excluded): node_modules/, .git/, dist/, build/, *.lock + +# Exclude Nuxt generated dirs +.nuxt/ +.output/ diff --git a/nuxt-api/.understand-anything/knowledge-graph.json b/nuxt-api/.understand-anything/knowledge-graph.json new file mode 100644 index 00000000..a8de7bb6 --- /dev/null +++ b/nuxt-api/.understand-anything/knowledge-graph.json @@ -0,0 +1,762 @@ +{ + "nodes": [ + { + "id": "shared/types.ts", + "type": "file", + "name": "shared/types.ts", + "summary": "Central shared type definitions and constants for the application tracker domain. Defines Application, InterviewStage, ApplicationEvent, ApplicationSnapshot interfaces; ApplicationStatus, CompanyCategory, JobSource enums; ImmerPatch and FieldChange types for event sourcing; FilterState and PaginatedResponse generics; and display-label constant arrays for all enum values.", + "tags": ["types", "shared", "domain", "event-sourcing", "enums"], + "filePath": "shared/types.ts", + "language": "TypeScript", + "exports": ["Application", "InterviewStage", "ApplicationEvent", "ApplicationSnapshot", "ApplicationStatus", "CompanyCategory", "JobSource", "ImmerPatch", "FieldChange", "FilterState", "PaginatedResponse", "CreateApplicationInput", "UpdateApplicationInput", "CreateInterviewStageInput", "UpdateInterviewStageInput", "APPLICATION_STATUSES", "COMPANY_CATEGORIES", "JOB_SOURCES"] + }, + { + "id": "server/db/schema.ts", + "type": "schema", + "name": "server/db/schema.ts", + "summary": "Drizzle ORM schema for the vue_nuxt PostgreSQL schema. Defines four tables: applications (main entity with status, company info, salary, dates), interview_stages (linked stages per application), application_events (event-sourcing log with JSON patches and field changes), and application_snapshots (periodic state snapshots at every 50 events). Includes pgSchema enums for status, company category, and job source. Exports inferred TypeScript types for all tables.", + "tags": ["database", "schema", "drizzle", "event-sourcing", "postgresql", "vue_nuxt"], + "filePath": "server/db/schema.ts", + "language": "TypeScript", + "exports": ["vueNuxtSchema", "applicationStatusEnum", "companyCategoryEnum", "jobSourceEnum", "applications", "interviewStages", "applicationEvents", "applicationSnapshots", "applicationsRelations", "interviewStagesRelations", "applicationEventsRelations", "applicationSnapshotsRelations", "DbApplication", "NewDbApplication", "DbInterviewStage", "NewDbInterviewStage", "DbApplicationEvent", "NewDbApplicationEvent", "DbApplicationSnapshot", "NewDbApplicationSnapshot"] + }, + { + "id": "server/db/client.ts", + "type": "file", + "name": "server/db/client.ts", + "summary": "Database connection module. Creates a postgres.js client from DATABASE_URL environment variable (defaulting to localhost app_tracker), wraps it with Drizzle ORM configured with the full schema for relational query support, and exports the db instance used throughout all services.", + "tags": ["database", "connection", "drizzle", "postgres"], + "filePath": "server/db/client.ts", + "language": "TypeScript", + "exports": ["db"] + }, + { + "id": "server/utils/validation.ts", + "type": "file", + "name": "server/utils/validation.ts", + "summary": "Zod validation schemas for all API request inputs and query parameters. Defines CreateApplicationSchema, UpdateApplicationSchema, ListApplicationsQuerySchema (with coercions for booleans/numbers and defaults for pagination/sort), CreateInterviewStageSchema, UpdateInterviewStageSchema, AppendEventSchema (with nested patch/change structures), ListEventsQuerySchema, and RestoreToEventSchema.", + "tags": ["validation", "zod", "schemas", "request-parsing"], + "filePath": "server/utils/validation.ts", + "language": "TypeScript", + "exports": ["ApplicationStatusSchema", "CompanyCategorySchema", "JobSourceSchema", "CreateApplicationSchema", "UpdateApplicationSchema", "ListApplicationsQuerySchema", "CreateInterviewStageSchema", "UpdateInterviewStageSchema", "AppendEventSchema", "ListEventsQuerySchema", "RestoreToEventSchema"] + }, + { + "id": "server/services/application.service.ts", + "type": "service", + "name": "server/services/application.service.ts", + "summary": "Core application CRUD service. Implements listApplications (with filtering by status/category/jobSource/skillsMatch, archive toggle, multi-column sort, and pagination), getApplication (with interview stages eager-loaded), createApplication (auto-sets dateApplied based on status), updateApplication (partial updates, forces dateApplied null when status=unsubmitted), deleteApplication, archiveApplication, and restoreApplication. Includes toApplicationResponse helper to transform DB rows to API shape.", + "tags": ["service", "crud", "applications", "filtering", "pagination"], + "filePath": "server/services/application.service.ts", + "language": "TypeScript", + "exports": ["listApplications", "getApplication", "createApplication", "updateApplication", "deleteApplication", "archiveApplication", "restoreApplication"] + }, + { + "id": "server/services/event.service.ts", + "type": "service", + "name": "server/services/event.service.ts", + "summary": "Event-sourcing service for application history. Implements appendEvent (auto-increments sequence per application, triggers snapshot creation every 50 events), listEvents (paginated newest-first), getLatestSnapshot (finds nearest snapshot at or before a sequence), and restoreToEvent (complex: locates nearest snapshot, replays forward patches OR applies inverse patches backward, updates DB state, deletes future events/snapshots, appends a Restored event). Uses Immer's applyPatches for state reconstruction.", + "tags": ["service", "event-sourcing", "immer", "patches", "snapshots", "history"], + "filePath": "server/services/event.service.ts", + "language": "TypeScript", + "exports": ["appendEvent", "listEvents", "getLatestSnapshot", "restoreToEvent"] + }, + { + "id": "server/services/interview-stage.service.ts", + "type": "service", + "name": "server/services/interview-stage.service.ts", + "summary": "Service for managing interview stages within an application. Implements createInterviewStage (verifies application exists, supports optional explicit UUID), updateInterviewStage (partial updates with application ownership check), and deleteInterviewStage. All mutating operations also update the parent application's updatedAt timestamp.", + "tags": ["service", "interview-stages", "crud"], + "filePath": "server/services/interview-stage.service.ts", + "language": "TypeScript", + "exports": ["createInterviewStage", "updateInterviewStage", "deleteInterviewStage"] + }, + { + "id": "server/api/health.get.ts", + "type": "endpoint", + "name": "GET /api/health", + "summary": "Health check endpoint returning status 'ok' and current timestamp. Used by monitoring and load balancers to verify the server is running.", + "tags": ["api", "health", "monitoring"], + "filePath": "server/api/health.get.ts", + "language": "TypeScript", + "httpMethod": "GET", + "route": "/api/health" + }, + { + "id": "server/api/applications/index.get.ts", + "type": "endpoint", + "name": "GET /api/applications", + "summary": "List applications endpoint with rich query filtering. Parses and validates query parameters via ListApplicationsQuerySchema (status, companyCategory, jobSource, skillsMatchMin, includeArchived, sortBy, sortDir, page, limit). Delegates to listApplications service. Returns paginated PaginatedResponse with items, page, limit, total.", + "tags": ["api", "applications", "list", "pagination", "filtering"], + "filePath": "server/api/applications/index.get.ts", + "language": "TypeScript", + "httpMethod": "GET", + "route": "/api/applications" + }, + { + "id": "server/api/applications/index.post.ts", + "type": "endpoint", + "name": "POST /api/applications", + "summary": "Create application endpoint. Validates request body with CreateApplicationSchema, calls createApplication service, returns 201 with created application. Handles ZodError with 400 validation_error response.", + "tags": ["api", "applications", "create"], + "filePath": "server/api/applications/index.post.ts", + "language": "TypeScript", + "httpMethod": "POST", + "route": "/api/applications" + }, + { + "id": "server/api/applications/[id].get.ts", + "type": "endpoint", + "name": "GET /api/applications/:id", + "summary": "Get single application by ID. Returns 400 if id missing, 404 if not found, otherwise full Application object with interview stages.", + "tags": ["api", "applications", "get"], + "filePath": "server/api/applications/[id].get.ts", + "language": "TypeScript", + "httpMethod": "GET", + "route": "/api/applications/:id" + }, + { + "id": "server/api/applications/[id].patch.ts", + "type": "endpoint", + "name": "PATCH /api/applications/:id", + "summary": "Update application endpoint. Validates body with UpdateApplicationSchema, calls updateApplication service. Returns 404 if not found, 400 on validation error, otherwise updated Application.", + "tags": ["api", "applications", "update"], + "filePath": "server/api/applications/[id].patch.ts", + "language": "TypeScript", + "httpMethod": "PATCH", + "route": "/api/applications/:id" + }, + { + "id": "server/api/applications/[id].delete.ts", + "type": "endpoint", + "name": "DELETE /api/applications/:id", + "summary": "Delete application endpoint. Returns 404 if not found, 204 No Content on success.", + "tags": ["api", "applications", "delete"], + "filePath": "server/api/applications/[id].delete.ts", + "language": "TypeScript", + "httpMethod": "DELETE", + "route": "/api/applications/:id" + }, + { + "id": "server/api/applications/[id]/archive.post.ts", + "type": "endpoint", + "name": "POST /api/applications/:id/archive", + "summary": "Archive an application (soft delete). Sets isArchived=true and updates updatedAt. Returns 404 if not found, otherwise updated application.", + "tags": ["api", "applications", "archive"], + "filePath": "server/api/applications/[id]/archive.post.ts", + "language": "TypeScript", + "httpMethod": "POST", + "route": "/api/applications/:id/archive" + }, + { + "id": "server/api/applications/[id]/restore.post.ts", + "type": "endpoint", + "name": "POST /api/applications/:id/restore", + "summary": "Restore an archived application. Sets isArchived=false. Returns 404 if not found, otherwise updated application.", + "tags": ["api", "applications", "restore", "archive"], + "filePath": "server/api/applications/[id]/restore.post.ts", + "language": "TypeScript", + "httpMethod": "POST", + "route": "/api/applications/:id/restore" + }, + { + "id": "server/api/applications/recreate.post.ts", + "type": "endpoint", + "name": "POST /api/applications/recreate", + "summary": "Recreate an application with a specific UUID (idempotent import/sync endpoint). Accepts a full Application body including id, companyName, positionTitle, all optional fields, and interviewStages. Returns 409 if ID already exists, 400 if required fields missing, 201 on success. Uses a DB transaction to insert application + stages atomically.", + "tags": ["api", "applications", "import", "sync", "transaction"], + "filePath": "server/api/applications/recreate.post.ts", + "language": "TypeScript", + "httpMethod": "POST", + "route": "/api/applications/recreate" + }, + { + "id": "server/api/applications/[id]/events/index.get.ts", + "type": "endpoint", + "name": "GET /api/applications/:id/events", + "summary": "List events for an application, paginated newest-first. Query params: page, limit. Returns events array with total, page, limit.", + "tags": ["api", "events", "event-sourcing", "history"], + "filePath": "server/api/applications/[id]/events/index.get.ts", + "language": "TypeScript", + "httpMethod": "GET", + "route": "/api/applications/:id/events" + }, + { + "id": "server/api/applications/[id]/events/index.post.ts", + "type": "endpoint", + "name": "POST /api/applications/:id/events", + "summary": "Append an event to an application's history. Body must include description, changes (field-level diff), patches (Immer forward patches), inversePatches (Immer backward patches). Returns 201 with created event.", + "tags": ["api", "events", "event-sourcing", "immer"], + "filePath": "server/api/applications/[id]/events/index.post.ts", + "language": "TypeScript", + "httpMethod": "POST", + "route": "/api/applications/:id/events" + }, + { + "id": "server/api/applications/[id]/events/restore.post.ts", + "type": "endpoint", + "name": "POST /api/applications/:id/events/restore", + "summary": "Restore application state to a specific event sequence. Body: { targetSequence }. Triggers full event replay logic: finds nearest snapshot, applies forward or inverse patches, updates DB, prunes future events/snapshots, and appends a restore marker event.", + "tags": ["api", "events", "event-sourcing", "restore", "time-travel"], + "filePath": "server/api/applications/[id]/events/restore.post.ts", + "language": "TypeScript", + "httpMethod": "POST", + "route": "/api/applications/:id/events/restore" + }, + { + "id": "server/api/applications/[id]/snapshots/latest.get.ts", + "type": "endpoint", + "name": "GET /api/applications/:id/snapshots/latest", + "summary": "Get the latest application snapshot at or before an optional beforeSequence query param. Returns the snapshot object or null if none exists.", + "tags": ["api", "snapshots", "event-sourcing"], + "filePath": "server/api/applications/[id]/snapshots/latest.get.ts", + "language": "TypeScript", + "httpMethod": "GET", + "route": "/api/applications/:id/snapshots/latest" + }, + { + "id": "server/api/applications/[id]/interview-stages/index.post.ts", + "type": "endpoint", + "name": "POST /api/applications/:id/interview-stages", + "summary": "Create an interview stage for an application. Validates body with CreateInterviewStageSchema. Returns 404 if application not found, 201 with created stage.", + "tags": ["api", "interview-stages", "create"], + "filePath": "server/api/applications/[id]/interview-stages/index.post.ts", + "language": "TypeScript", + "httpMethod": "POST", + "route": "/api/applications/:id/interview-stages" + }, + { + "id": "server/api/applications/[id]/interview-stages/[stageId].patch.ts", + "type": "endpoint", + "name": "PATCH /api/applications/:id/interview-stages/:stageId", + "summary": "Update an interview stage. Validates body with UpdateInterviewStageSchema. Returns 404 if stage not found or doesn't belong to application.", + "tags": ["api", "interview-stages", "update"], + "filePath": "server/api/applications/[id]/interview-stages/[stageId].patch.ts", + "language": "TypeScript", + "httpMethod": "PATCH", + "route": "/api/applications/:id/interview-stages/:stageId" + }, + { + "id": "server/api/applications/[id]/interview-stages/[stageId].delete.ts", + "type": "endpoint", + "name": "DELETE /api/applications/:id/interview-stages/:stageId", + "summary": "Delete an interview stage. Returns 404 if not found, 204 on success. Also bumps parent application's updatedAt.", + "tags": ["api", "interview-stages", "delete"], + "filePath": "server/api/applications/[id]/interview-stages/[stageId].delete.ts", + "language": "TypeScript", + "httpMethod": "DELETE", + "route": "/api/applications/:id/interview-stages/:stageId" + }, + { + "id": "nuxt.config.ts", + "type": "config", + "name": "nuxt.config.ts", + "summary": "Nuxt 3 configuration. Sets devServer port to 5040, disables SSR (API-only mode), and configures Nitro CORS route rules for /api/** to allow requests from vue-ui dev server at localhost:3020 (GET, POST, PATCH, DELETE, OPTIONS).", + "tags": ["config", "nuxt", "cors", "nitro"], + "filePath": "nuxt.config.ts", + "language": "TypeScript" + }, + { + "id": "drizzle.config.ts", + "type": "config", + "name": "drizzle.config.ts", + "summary": "Drizzle Kit configuration for schema generation and migration. Points to server/db/schema.ts, outputs migrations to server/db/migrations, targets PostgreSQL dialect, filters to vue_nuxt schema only.", + "tags": ["config", "drizzle", "migrations", "database"], + "filePath": "drizzle.config.ts", + "language": "TypeScript" + }, + { + "id": "server/db/migrations/0000_rich_bastion.sql", + "type": "file", + "name": "migrations/0000_rich_bastion.sql", + "summary": "Initial database migration. Creates the vue_nuxt schema and its three core enums (application_status, company_category, job_source), the applications table, and the interview_stages table with foreign key to applications.", + "tags": ["migration", "sql", "database", "schema"], + "filePath": "server/db/migrations/0000_rich_bastion.sql", + "language": "SQL" + }, + { + "id": "server/db/migrations/0001_perpetual_sister_grimm.sql", + "type": "file", + "name": "migrations/0001_perpetual_sister_grimm.sql", + "summary": "Second migration adding event sourcing tables: application_events (with jsonb patches/inverse_patches/changes, unique constraint on applicationId+sequence) and application_snapshots (with jsonb state, unique on applicationId+atSequence).", + "tags": ["migration", "sql", "event-sourcing", "database"], + "filePath": "server/db/migrations/0001_perpetual_sister_grimm.sql", + "language": "SQL" + }, + { + "id": "tests/event.service.test.ts", + "type": "file", + "name": "tests/event.service.test.ts", + "summary": "Integration tests for event.service against a real PostgreSQL database. Tests appendEvent (sequence auto-increment, per-application isolation, snapshot trigger at 50 events), listEvents (descending order, pagination), getLatestSnapshot (null when none, boundary conditions), restoreToEvent (state reconstruction, pruning future events), and cascade delete behavior. Uses vitest with beforeEach/afterEach for test application lifecycle.", + "tags": ["tests", "integration", "event-sourcing", "vitest"], + "filePath": "tests/event.service.test.ts", + "language": "TypeScript" + }, + { + "id": "eslint.config.js", + "type": "config", + "name": "eslint.config.js", + "summary": "ESLint flat config for the nuxt-api project using @eslint/js and typescript-eslint.", + "tags": ["config", "lint", "eslint"], + "filePath": "eslint.config.js", + "language": "JavaScript" + }, + { + "id": "tsconfig.json", + "type": "config", + "name": "tsconfig.json", + "summary": "TypeScript project configuration for nuxt-api with strict mode and Nuxt-generated path aliases.", + "tags": ["config", "typescript"], + "filePath": "tsconfig.json", + "language": "JSON" + }, + { + "id": "package.json", + "type": "config", + "name": "package.json", + "summary": "Package manifest. Dependencies: nuxt, drizzle-orm, postgres, zod, immer, vue, vue-router. Dev: drizzle-kit, eslint, typescript. Scripts: build, dev, generate, preview, lint, db:generate, db:migrate, db:studio. Overrides vite to 7.3.2.", + "tags": ["config", "dependencies", "npm"], + "filePath": "package.json", + "language": "JSON" + } + ], + "edges": [ + { + "source": "server/db/client.ts", + "target": "server/db/schema.ts", + "type": "imports", + "label": "imports schema for Drizzle relational queries" + }, + { + "source": "server/db/schema.ts", + "target": "shared/types.ts", + "type": "imports", + "label": "imports FieldChange, ImmerPatch, Application types for JSONB column typing" + }, + { + "source": "server/services/application.service.ts", + "target": "server/db/client.ts", + "type": "imports", + "label": "imports db instance" + }, + { + "source": "server/services/application.service.ts", + "target": "server/db/schema.ts", + "type": "imports", + "label": "imports table references and DB types" + }, + { + "source": "server/services/application.service.ts", + "target": "shared/types.ts", + "type": "imports", + "label": "imports Application type for return signatures" + }, + { + "source": "server/services/application.service.ts", + "target": "server/utils/validation.ts", + "type": "imports", + "label": "imports Zod schema types for parameter typing" + }, + { + "source": "server/services/event.service.ts", + "target": "server/db/client.ts", + "type": "imports", + "label": "imports db instance" + }, + { + "source": "server/services/event.service.ts", + "target": "server/db/schema.ts", + "type": "imports", + "label": "imports applicationEvents, applicationSnapshots tables" + }, + { + "source": "server/services/event.service.ts", + "target": "server/services/application.service.ts", + "type": "imports", + "label": "imports getApplication for snapshot creation and restore" + }, + { + "source": "server/services/event.service.ts", + "target": "shared/types.ts", + "type": "imports", + "label": "imports ApplicationEvent, ApplicationSnapshot, FieldChange, ImmerPatch types" + }, + { + "source": "server/services/interview-stage.service.ts", + "target": "server/db/client.ts", + "type": "imports", + "label": "imports db instance" + }, + { + "source": "server/services/interview-stage.service.ts", + "target": "server/db/schema.ts", + "type": "imports", + "label": "imports applications and interviewStages tables" + }, + { + "source": "server/services/interview-stage.service.ts", + "target": "shared/types.ts", + "type": "imports", + "label": "imports InterviewStage type" + }, + { + "source": "server/services/interview-stage.service.ts", + "target": "server/utils/validation.ts", + "type": "imports", + "label": "imports CreateInterviewStageSchema, UpdateInterviewStageSchema types" + }, + { + "source": "server/api/applications/index.get.ts", + "target": "server/utils/validation.ts", + "type": "imports", + "label": "imports ListApplicationsQuerySchema" + }, + { + "source": "server/api/applications/index.get.ts", + "target": "server/services/application.service.ts", + "type": "imports", + "label": "calls listApplications" + }, + { + "source": "server/api/applications/index.post.ts", + "target": "server/utils/validation.ts", + "type": "imports", + "label": "imports CreateApplicationSchema" + }, + { + "source": "server/api/applications/index.post.ts", + "target": "server/services/application.service.ts", + "type": "imports", + "label": "calls createApplication" + }, + { + "source": "server/api/applications/[id].get.ts", + "target": "server/services/application.service.ts", + "type": "imports", + "label": "calls getApplication" + }, + { + "source": "server/api/applications/[id].patch.ts", + "target": "server/utils/validation.ts", + "type": "imports", + "label": "imports UpdateApplicationSchema" + }, + { + "source": "server/api/applications/[id].patch.ts", + "target": "server/services/application.service.ts", + "type": "imports", + "label": "calls updateApplication" + }, + { + "source": "server/api/applications/[id].delete.ts", + "target": "server/services/application.service.ts", + "type": "imports", + "label": "calls deleteApplication" + }, + { + "source": "server/api/applications/[id]/archive.post.ts", + "target": "server/services/application.service.ts", + "type": "imports", + "label": "calls archiveApplication" + }, + { + "source": "server/api/applications/[id]/restore.post.ts", + "target": "server/services/application.service.ts", + "type": "imports", + "label": "calls restoreApplication" + }, + { + "source": "server/api/applications/recreate.post.ts", + "target": "server/db/client.ts", + "type": "imports", + "label": "imports db for direct transaction" + }, + { + "source": "server/api/applications/recreate.post.ts", + "target": "server/db/schema.ts", + "type": "imports", + "label": "imports applications and interviewStages tables" + }, + { + "source": "server/api/applications/recreate.post.ts", + "target": "server/services/application.service.ts", + "type": "imports", + "label": "calls getApplication for duplicate check" + }, + { + "source": "server/api/applications/recreate.post.ts", + "target": "shared/types.ts", + "type": "imports", + "label": "imports Application type for request body typing" + }, + { + "source": "server/api/applications/[id]/events/index.get.ts", + "target": "server/utils/validation.ts", + "type": "imports", + "label": "imports ListEventsQuerySchema" + }, + { + "source": "server/api/applications/[id]/events/index.get.ts", + "target": "server/services/event.service.ts", + "type": "imports", + "label": "calls listEvents" + }, + { + "source": "server/api/applications/[id]/events/index.post.ts", + "target": "server/utils/validation.ts", + "type": "imports", + "label": "imports AppendEventSchema" + }, + { + "source": "server/api/applications/[id]/events/index.post.ts", + "target": "server/services/event.service.ts", + "type": "imports", + "label": "calls appendEvent" + }, + { + "source": "server/api/applications/[id]/events/restore.post.ts", + "target": "server/utils/validation.ts", + "type": "imports", + "label": "imports RestoreToEventSchema" + }, + { + "source": "server/api/applications/[id]/events/restore.post.ts", + "target": "server/services/event.service.ts", + "type": "imports", + "label": "calls restoreToEvent" + }, + { + "source": "server/api/applications/[id]/snapshots/latest.get.ts", + "target": "server/services/event.service.ts", + "type": "imports", + "label": "calls getLatestSnapshot" + }, + { + "source": "server/api/applications/[id]/interview-stages/index.post.ts", + "target": "server/utils/validation.ts", + "type": "imports", + "label": "imports CreateInterviewStageSchema" + }, + { + "source": "server/api/applications/[id]/interview-stages/index.post.ts", + "target": "server/services/interview-stage.service.ts", + "type": "imports", + "label": "calls createInterviewStage" + }, + { + "source": "server/api/applications/[id]/interview-stages/[stageId].patch.ts", + "target": "server/utils/validation.ts", + "type": "imports", + "label": "imports UpdateInterviewStageSchema" + }, + { + "source": "server/api/applications/[id]/interview-stages/[stageId].patch.ts", + "target": "server/services/interview-stage.service.ts", + "type": "imports", + "label": "calls updateInterviewStage" + }, + { + "source": "server/api/applications/[id]/interview-stages/[stageId].delete.ts", + "target": "server/services/interview-stage.service.ts", + "type": "imports", + "label": "calls deleteInterviewStage" + }, + { + "source": "server/db/migrations/0000_rich_bastion.sql", + "target": "server/db/schema.ts", + "type": "implements", + "label": "SQL implementation of initial schema" + }, + { + "source": "server/db/migrations/0001_perpetual_sister_grimm.sql", + "target": "server/db/schema.ts", + "type": "implements", + "label": "SQL implementation of event sourcing tables" + }, + { + "source": "drizzle.config.ts", + "target": "server/db/schema.ts", + "type": "references", + "label": "points to schema file for migration generation" + }, + { + "source": "nuxt.config.ts", + "target": "server/api/applications/index.get.ts", + "type": "configures", + "label": "Nitro CORS rules apply to /api/** routes" + }, + { + "source": "tests/event.service.test.ts", + "target": "server/services/event.service.ts", + "type": "tests", + "label": "integration tests for all event service functions" + }, + { + "source": "tests/event.service.test.ts", + "target": "server/services/application.service.ts", + "type": "tests", + "label": "uses application service for test fixture setup" + }, + { + "source": "tests/event.service.test.ts", + "target": "server/db/client.ts", + "type": "imports", + "label": "direct DB access for cascade delete verification" + }, + { + "source": "tests/event.service.test.ts", + "target": "server/db/schema.ts", + "type": "imports", + "label": "imports table references for direct queries" + }, + { + "source": "tests/event.service.test.ts", + "target": "shared/types.ts", + "type": "imports", + "label": "imports ImmerPatch type for test data" + } + ], + "layers": [ + { + "id": "layer-config", + "name": "Configuration", + "description": "Project-level configuration files for Nuxt, Drizzle, TypeScript, ESLint, and package management.", + "nodeIds": [ + "nuxt.config.ts", + "drizzle.config.ts", + "eslint.config.js", + "tsconfig.json", + "package.json" + ] + }, + { + "id": "layer-shared", + "name": "Shared Types", + "description": "Domain type definitions and constants shared between frontend (vue-ui) and backend (nuxt-api).", + "nodeIds": [ + "shared/types.ts" + ] + }, + { + "id": "layer-database", + "name": "Database Layer", + "description": "Drizzle ORM schema, PostgreSQL client connection, and SQL migration files defining the vue_nuxt schema.", + "nodeIds": [ + "server/db/schema.ts", + "server/db/client.ts", + "server/db/migrations/0000_rich_bastion.sql", + "server/db/migrations/0001_perpetual_sister_grimm.sql" + ] + }, + { + "id": "layer-validation", + "name": "Validation", + "description": "Zod schemas for validating all inbound API requests and query parameters.", + "nodeIds": [ + "server/utils/validation.ts" + ] + }, + { + "id": "layer-services", + "name": "Service Layer", + "description": "Business logic services implementing CRUD operations, event sourcing, and interview stage management.", + "nodeIds": [ + "server/services/application.service.ts", + "server/services/event.service.ts", + "server/services/interview-stage.service.ts" + ] + }, + { + "id": "layer-api", + "name": "API Routes", + "description": "H3 event handlers (Nuxt server routes) implementing REST endpoints for applications, events, snapshots, and interview stages.", + "nodeIds": [ + "server/api/health.get.ts", + "server/api/applications/index.get.ts", + "server/api/applications/index.post.ts", + "server/api/applications/[id].get.ts", + "server/api/applications/[id].patch.ts", + "server/api/applications/[id].delete.ts", + "server/api/applications/[id]/archive.post.ts", + "server/api/applications/[id]/restore.post.ts", + "server/api/applications/recreate.post.ts", + "server/api/applications/[id]/events/index.get.ts", + "server/api/applications/[id]/events/index.post.ts", + "server/api/applications/[id]/events/restore.post.ts", + "server/api/applications/[id]/snapshots/latest.get.ts", + "server/api/applications/[id]/interview-stages/index.post.ts", + "server/api/applications/[id]/interview-stages/[stageId].patch.ts", + "server/api/applications/[id]/interview-stages/[stageId].delete.ts" + ] + }, + { + "id": "layer-tests", + "name": "Tests", + "description": "Integration tests for the event sourcing service running against a real PostgreSQL database.", + "nodeIds": [ + "tests/event.service.test.ts" + ] + } + ], + "tour": [ + { + "order": 1, + "title": "Start with Shared Types", + "description": "Begin at shared/types.ts to understand the full domain model: Application, InterviewStage, ApplicationEvent, ApplicationSnapshot interfaces, plus the enums and constants used throughout both frontend and backend.", + "nodeIds": ["shared/types.ts"] + }, + { + "order": 2, + "title": "Database Schema", + "description": "Explore server/db/schema.ts to see how the domain model maps to four PostgreSQL tables (applications, interview_stages, application_events, application_snapshots) in the vue_nuxt schema using Drizzle ORM. Note how JSON columns are typed with shared types.", + "nodeIds": ["server/db/schema.ts", "server/db/client.ts"] + }, + { + "order": 3, + "title": "Configuration", + "description": "Review nuxt.config.ts and drizzle.config.ts to understand the server setup: API-only mode (no SSR), port 5040, CORS for vue-ui at localhost:3020, and how Drizzle Kit is configured to manage migrations for the vue_nuxt schema.", + "nodeIds": ["nuxt.config.ts", "drizzle.config.ts"] + }, + { + "order": 4, + "title": "Request Validation", + "description": "Read server/utils/validation.ts to see all Zod schemas. Note the ListApplicationsQuerySchema with coercions (booleans, numbers) and defaults for pagination, and the AppendEventSchema that validates Immer patch structures.", + "nodeIds": ["server/utils/validation.ts"] + }, + { + "order": 5, + "title": "Core Application Service", + "description": "Study server/services/application.service.ts for the main CRUD logic: filtering with dynamic conditions, pagination, the toApplicationResponse shape transformation, and the status→dateApplied business rule.", + "nodeIds": ["server/services/application.service.ts"] + }, + { + "order": 6, + "title": "Event Sourcing Service", + "description": "The most complex file: server/services/event.service.ts. Learn how events are appended with auto-incrementing sequences, how snapshots are created every 50 events, and how restoreToEvent reconstructs past state using either forward patch replay (from snapshot) or backward inverse patch application.", + "nodeIds": ["server/services/event.service.ts"] + }, + { + "order": 7, + "title": "Interview Stage Service", + "description": "server/services/interview-stage.service.ts shows the pattern for nested resource management: ownership verification, partial updates, and propagating updatedAt to the parent application.", + "nodeIds": ["server/services/interview-stage.service.ts"] + }, + { + "order": 8, + "title": "API Route Handlers", + "description": "Walk through the API routes layer. Each file follows the same pattern: extract route param, parse/validate input, call service, handle ZodError with 400, handle not-found with 404. The recreate endpoint is notable for using a DB transaction directly.", + "nodeIds": [ + "server/api/applications/index.get.ts", + "server/api/applications/index.post.ts", + "server/api/applications/[id].get.ts", + "server/api/applications/recreate.post.ts", + "server/api/applications/[id]/events/restore.post.ts" + ] + }, + { + "order": 9, + "title": "Integration Tests", + "description": "tests/event.service.test.ts demonstrates the event sourcing contract: sequence isolation per application, snapshot trigger conditions, restore boundary behaviors, and cascade delete. Each test uses real DB fixtures — no mocks.", + "nodeIds": ["tests/event.service.test.ts"] + } + ] +} diff --git a/nuxt-api/.understand-anything/meta.json b/nuxt-api/.understand-anything/meta.json new file mode 100644 index 00000000..03408438 --- /dev/null +++ b/nuxt-api/.understand-anything/meta.json @@ -0,0 +1,38 @@ +{ + "lastAnalyzedAt": "2026-05-04T00:00:00.000Z", + "gitCommitHash": "e5054096efec8165e7d4cfe11fc103f77ad2d98f", + "version": "1.0.0", + "analyzedFiles": [ + "nuxt.config.ts", + "drizzle.config.ts", + "eslint.config.js", + "tsconfig.json", + "package.json", + "shared/types.ts", + "server/db/client.ts", + "server/db/schema.ts", + "server/db/migrations/0000_rich_bastion.sql", + "server/db/migrations/0001_perpetual_sister_grimm.sql", + "server/utils/validation.ts", + "server/services/application.service.ts", + "server/services/event.service.ts", + "server/services/interview-stage.service.ts", + "server/api/health.get.ts", + "server/api/applications/index.get.ts", + "server/api/applications/index.post.ts", + "server/api/applications/[id].get.ts", + "server/api/applications/[id].patch.ts", + "server/api/applications/[id].delete.ts", + "server/api/applications/[id]/archive.post.ts", + "server/api/applications/[id]/restore.post.ts", + "server/api/applications/recreate.post.ts", + "server/api/applications/[id]/events/index.get.ts", + "server/api/applications/[id]/events/index.post.ts", + "server/api/applications/[id]/events/restore.post.ts", + "server/api/applications/[id]/snapshots/latest.get.ts", + "server/api/applications/[id]/interview-stages/index.post.ts", + "server/api/applications/[id]/interview-stages/[stageId].patch.ts", + "server/api/applications/[id]/interview-stages/[stageId].delete.ts", + "tests/event.service.test.ts" + ] +} diff --git a/rails-api/.understand-anything/.understandignore b/rails-api/.understand-anything/.understandignore new file mode 100644 index 00000000..78d4f9ab --- /dev/null +++ b/rails-api/.understand-anything/.understandignore @@ -0,0 +1,6 @@ +# .understandignore — patterns for files/dirs to exclude from analysis +# Built-in defaults (always excluded): node_modules/, .git/, dist/, build/, *.lock + +# Exclude Rails runtime dirs +tmp/ +log/ diff --git a/rails-api/.understand-anything/knowledge-graph.json b/rails-api/.understand-anything/knowledge-graph.json new file mode 100644 index 00000000..3582212f --- /dev/null +++ b/rails-api/.understand-anything/knowledge-graph.json @@ -0,0 +1,830 @@ +{ + "nodes": [ + { + "id": "file:config.ru", + "type": "file", + "name": "config.ru", + "filePath": "config.ru", + "summary": "Rack entry point that loads the Rails environment and mounts the Rails application as the Rack application. This is the outermost boot file executed by Puma.", + "tags": ["rack", "entrypoint", "boot", "puma"], + "complexity": "low" + }, + { + "id": "file:Gemfile", + "type": "config", + "name": "Gemfile", + "filePath": "Gemfile", + "summary": "Ruby dependency manifest declaring Rails 8.0.4.1, PostgreSQL adapter (pg), Puma web server, rack-cors for CORS handling, bootsnap for startup optimization, and dev/test gems including RSpec, Rubocop, and bundler-audit.", + "tags": ["dependencies", "ruby", "bundler", "gems"], + "complexity": "low" + }, + { + "id": "file:config/application.rb", + "type": "config", + "name": "config/application.rb", + "filePath": "config/application.rb", + "summary": "Rails application class definition for the RailsApi module. Configures api_only mode (no views/assets), UTC timezone, SQL schema format, and RSpec as the test framework generator. Requires only the essential Rails railties (ActiveModel, ActiveRecord, ActionController).", + "tags": ["rails", "configuration", "api-only", "application"], + "complexity": "low" + }, + { + "id": "file:config/routes.rb", + "type": "config", + "name": "config/routes.rb", + "filePath": "config/routes.rb", + "summary": "Rails router defining all API endpoints. Health check at GET /health. Applications resources (index, show, create, update, destroy) with custom member actions (archive, restore) and collection actions (export, sample_csv, import as csv_not_implemented stubs). Nested resources for interview-stages and history with a history/restore action.", + "tags": ["routes", "router", "api", "endpoints", "rest"], + "complexity": "moderate" + }, + { + "id": "file:config/database.yml", + "type": "config", + "name": "config/database.yml", + "filePath": "config/database.yml", + "summary": "PostgreSQL database configuration. All environments use schema_search_path: ruby_rails,public for schema isolation. Development and test use DATABASE_URL env var with localhost fallback. Production requires DATABASE_URL explicitly.", + "tags": ["database", "postgresql", "configuration", "schema"], + "complexity": "low" + }, + { + "id": "file:config/boot.rb", + "type": "config", + "name": "config/boot.rb", + "filePath": "config/boot.rb", + "summary": "Rails boot file that sets BUNDLE_GEMFILE and requires bundler/setup and bootsnap (in non-production environments) for faster boot times.", + "tags": ["boot", "bundler", "bootsnap", "configuration"], + "complexity": "low" + }, + { + "id": "file:config/environment.rb", + "type": "config", + "name": "config/environment.rb", + "filePath": "config/environment.rb", + "summary": "Rails environment initialization file that requires config/application.rb and calls RailsApi::Application.initialize! to boot the full application.", + "tags": ["environment", "boot", "initialization", "rails"], + "complexity": "low" + }, + { + "id": "file:config/initializers/cors.rb", + "type": "config", + "name": "config/initializers/cors.rb", + "filePath": "config/initializers/cors.rb", + "summary": "CORS configuration using rack-cors middleware. Allows all HTTP methods from localhost origins matching ports 3000-3090, 3100, or 5173 (standard UI dev server ports for all frontend stacks in this monorepo).", + "tags": ["cors", "middleware", "security", "rack-cors", "initializer"], + "complexity": "low" + }, + { + "id": "file:config/initializers/active_record_migration_tables.rb", + "type": "config", + "name": "config/initializers/active_record_migration_tables.rb", + "filePath": "config/initializers/active_record_migration_tables.rb", + "summary": "Schema isolation initializer. Creates the ruby_rails schema if not exists before ActiveRecord places its metadata tables. Overrides schema_migrations_table_name and internal_metadata_table_name to use ruby_rails schema prefix, preventing metadata table collision with other stacks sharing the app_tracker database.", + "tags": ["activerecord", "schema", "migration", "isolation", "initializer", "postgresql"], + "complexity": "moderate" + }, + { + "id": "file:config/puma.rb", + "type": "config", + "name": "config/puma.rb", + "filePath": "config/puma.rb", + "summary": "Puma web server configuration file.", + "tags": ["puma", "server", "configuration"], + "complexity": "low" + }, + { + "id": "file:config/environments/development.rb", + "type": "config", + "name": "config/environments/development.rb", + "filePath": "config/environments/development.rb", + "summary": "Rails development environment configuration.", + "tags": ["environment", "development", "configuration"], + "complexity": "low" + }, + { + "id": "file:config/environments/test.rb", + "type": "config", + "name": "config/environments/test.rb", + "filePath": "config/environments/test.rb", + "summary": "Rails test environment configuration.", + "tags": ["environment", "test", "configuration", "rspec"], + "complexity": "low" + }, + { + "id": "file:config/environments/production.rb", + "type": "config", + "name": "config/environments/production.rb", + "filePath": "config/environments/production.rb", + "summary": "Rails production environment configuration.", + "tags": ["environment", "production", "configuration"], + "complexity": "low" + }, + { + "id": "file:app/controllers/application_controller.rb", + "type": "file", + "name": "ApplicationController", + "filePath": "app/controllers/application_controller.rb", + "summary": "Base controller inheriting from ActionController::API. Provides shared error handling: rescues ActiveRecord::RecordInvalid (400 with validation details), RecordNotFound (404), and ApiParams::InvalidDate (400). Provides json_payload helper for accessing request body. Formats validation errors with camelCase field names.", + "tags": ["controller", "base", "error-handling", "rest", "api"], + "complexity": "moderate" + }, + { + "id": "file:app/controllers/api/applications_controller.rb", + "type": "file", + "name": "Api::ApplicationsController", + "filePath": "app/controllers/api/applications_controller.rb", + "summary": "Main CRUD controller for job applications. Handles index (list with filters/sort/pagination via ApplicationService), show (single), create (201), update (PATCH), destroy (204), archive and restore custom actions. Stubs out CSV endpoints (export, import, sample_csv) as 501 Not Implemented. Eager loads interview_stages and application_snapshots.", + "tags": ["controller", "applications", "crud", "rest", "pagination"], + "complexity": "moderate" + }, + { + "id": "file:app/controllers/api/application_history_controller.rb", + "type": "file", + "name": "Api::ApplicationHistoryController", + "filePath": "app/controllers/api/application_history_controller.rb", + "summary": "Controller for application history/snapshot endpoints. GET index returns paginated snapshots with diff-based change entries (comparing each snapshot to its predecessor). POST restore validates the sequence number and delegates to ApplicationRestoreService to replay a historical state.", + "tags": ["controller", "history", "snapshots", "pagination", "restore"], + "complexity": "moderate" + }, + { + "id": "file:app/controllers/api/health_controller.rb", + "type": "file", + "name": "Api::HealthController", + "filePath": "app/controllers/api/health_controller.rb", + "summary": "Simple health check controller responding to GET /health with JSON {status: 'ok'}. Used by load balancers and monitoring systems to verify the API is running.", + "tags": ["controller", "health", "monitoring"], + "complexity": "low" + }, + { + "id": "file:app/controllers/api/interview_stages_controller.rb", + "type": "file", + "name": "Api::InterviewStagesController", + "filePath": "app/controllers/api/interview_stages_controller.rb", + "summary": "Nested controller for interview stages under /applications/:application_id/interview-stages. Handles create (201), update (200), and destroy (204) via InterviewStageService. Scopes stage lookup through the parent application for security.", + "tags": ["controller", "interview-stages", "nested", "crud"], + "complexity": "low" + }, + { + "id": "file:db/migrate/001_initial_schema.rb", + "type": "file", + "name": "001_initial_schema.rb", + "filePath": "db/migrate/001_initial_schema.rb", + "summary": "Initial Rails migration creating the ruby_rails PostgreSQL schema and its three tables: applications (UUID PK, status, company fields, salary range, timestamps), interview_stages (UUID PK, FK to applications, order, completion tracking, performance rating), and application_snapshots (UUID PK, FK to applications, sequence number, JSONB snapshot). Includes composite unique index on (application_id, sequence) for snapshot ordering. Uses IF NOT EXISTS for idempotent execution.", + "tags": ["migration", "database", "schema", "postgresql", "uuid", "jsonb"], + "complexity": "moderate" + }, + { + "id": "file:Rakefile", + "type": "config", + "name": "Rakefile", + "filePath": "Rakefile", + "summary": "Rails Rakefile that loads Rails tasks for db:migrate, db:create, test, etc.", + "tags": ["rake", "tasks", "build", "configuration"], + "complexity": "low" + }, + { + "id": "file:app/models/application_record.rb", + "type": "file", + "name": "ApplicationRecord", + "filePath": "app/models/application_record.rb", + "summary": "Abstract base model class inheriting from ActiveRecord::Base. All application models inherit from this to share ActiveRecord functionality. Marked as primary_abstract_class so Rails does not attempt to instantiate it directly.", + "tags": ["model", "activerecord", "base", "abstract"], + "complexity": "low" + }, + { + "id": "file:app/models/job_application.rb", + "type": "file", + "name": "JobApplication", + "filePath": "app/models/job_application.rb", + "summary": "Core domain model for a job application. Maps to ruby_rails.applications table. Defines domain constants (APPLICATION_STATUSES, COMPANY_CATEGORIES, JOB_SOURCES, TERMINAL_STATUSES). Has many interview_stages and application_snapshots with cascade delete. Validates presence/length of company_name and position_title, validates status inclusion, validates optional fields (company_category, job_source, skills_match rating 1-5, salary integers). Custom validations: salary_range_is_ordered (max >= min), urls_are_http (company/posting/career URLs).", + "tags": ["model", "activerecord", "domain", "job-application", "validation"], + "complexity": "moderate" + }, + { + "id": "file:app/models/application_snapshot.rb", + "type": "file", + "name": "ApplicationSnapshot", + "filePath": "app/models/application_snapshot.rb", + "summary": "Model for a point-in-time snapshot of a job application's state. Maps to ruby_rails.application_snapshots. Stores serialized application JSON in a JSONB column (snapshot). Each snapshot has a monotonically increasing sequence number per application. Used for history tracking and restore-to-version functionality.", + "tags": ["model", "activerecord", "history", "snapshot", "jsonb"], + "complexity": "low" + }, + { + "id": "file:app/models/interview_stage.rb", + "type": "file", + "name": "InterviewStage", + "filePath": "app/models/interview_stage.rb", + "summary": "Model for an individual interview stage within a job application. Maps to ruby_rails.interview_stages. Belongs to JobApplication. Fields: name (max 100 chars), order (integer >= 0), is_completed boolean, completed_date, notes (max 2000 chars), performance_rating (1-5 integer). Sorted by order for display.", + "tags": ["model", "activerecord", "interview-stages", "validation"], + "complexity": "low" + }, + { + "id": "file:app/serializers/application_serializer.rb", + "type": "file", + "name": "ApplicationSerializer", + "filePath": "app/serializers/application_serializer.rb", + "summary": "Pure Ruby class (no ActiveModel::Serializer) providing class-level methods to serialize domain objects to camelCase JSON hashes. Methods: application (full app with nested interview_stages sorted by order), interview_stage, history_entry (with changes diff), changes (computes field-by-field diff between consecutive snapshots including interview stage arrays). Formats Date as ISO-8601 date string and DateTime as UTC ISO-8601.", + "tags": ["serializer", "json", "camelcase", "presentation", "diff"], + "complexity": "moderate" + }, + { + "id": "file:app/services/api_params.rb", + "type": "file", + "name": "ApiParams", + "filePath": "app/services/api_params.rb", + "summary": "Module for parsing and mapping camelCase API request parameters to snake_case ActiveRecord attributes. Defines APPLICATION_FIELD_MAP and STAGE_FIELD_MAP for the two primary resource types. DATE_FIELDS list triggers date parsing through parse_date which validates YYYY-MM-DD format and raises InvalidDate (a custom StandardError) on invalid input.", + "tags": ["service", "params", "camelcase", "snakecase", "validation", "date-parsing"], + "complexity": "low" + }, + { + "id": "file:app/services/application_service.rb", + "type": "file", + "name": "ApplicationService", + "filePath": "app/services/application_service.rb", + "summary": "Primary service orchestrating all job application mutations. Methods: list (filter by status/category/source/skills_match, sort, paginate, return items+pagination meta), create (validate, set default status, apply date side effects, create default interview stages if interviewing, record snapshot), update (prevent terminal transitions, apply date side effects, create default stages on status change, record snapshot), archive (set is_archived=true, snapshot), restore (set is_archived=false, snapshot), delete (snapshot then destroy). All mutations wrapped in transactions. DEFAULT_STAGES array creates 6 standard interview stages when status moves to 'interviewing'.", + "tags": ["service", "application", "business-logic", "transaction", "pagination", "filtering"], + "complexity": "high" + }, + { + "id": "file:app/services/application_snapshot_service.rb", + "type": "file", + "name": "ApplicationSnapshotService", + "filePath": "app/services/application_snapshot_service.rb", + "summary": "Service for creating application history snapshots. Uses pessimistic locking (lock!) to safely compute the next sequence number and create an ApplicationSnapshot with the serialized current application state. Called by ApplicationService and InterviewStageService after every mutation.", + "tags": ["service", "snapshot", "history", "locking", "transaction"], + "complexity": "low" + }, + { + "id": "file:app/services/application_restore_service.rb", + "type": "file", + "name": "ApplicationRestoreService", + "filePath": "app/services/application_restore_service.rb", + "summary": "Service for restoring a job application to a historical snapshot state. Fetches the snapshot by sequence, extracts application attributes and interview stages from the JSONB snapshot data, replaces all current interview stages with the historical ones (preserving original UUIDs), updates the application record, and records a new snapshot describing the restore action.", + "tags": ["service", "restore", "history", "transaction", "interview-stages"], + "complexity": "moderate" + }, + { + "id": "file:app/services/interview_stage_service.rb", + "type": "file", + "name": "InterviewStageService", + "filePath": "app/services/interview_stage_service.rb", + "summary": "Service for managing individual interview stage lifecycle. Create wraps stage creation, touches application.updated_at, and records a snapshot. Update applies attribute changes, touches application.updated_at, and records a snapshot. Delete destroys the stage, touches application.updated_at, and records a snapshot. All operations are transactional.", + "tags": ["service", "interview-stages", "transaction", "snapshot"], + "complexity": "moderate" + }, + { + "id": "file:spec/spec_helper.rb", + "type": "file", + "name": "spec_helper.rb", + "filePath": "spec/spec_helper.rb", + "summary": "RSpec configuration file. Sets up expect syntax with chain clause descriptions, enables verify_partial_doubles for mock verification, and configures shared context metadata behavior.", + "tags": ["test", "rspec", "configuration", "spec"], + "complexity": "low" + }, + { + "id": "file:spec/rails_helper.rb", + "type": "file", + "name": "rails_helper.rb", + "filePath": "spec/rails_helper.rb", + "summary": "RSpec Rails helper that loads the Rails environment, requires rspec/rails, verifies test schema is current, and configures transactional fixtures for database isolation between tests.", + "tags": ["test", "rspec", "rails", "configuration", "database"], + "complexity": "low" + }, + { + "id": "file:spec/models/job_application_spec.rb", + "type": "file", + "name": "job_application_spec.rb", + "filePath": "spec/models/job_application_spec.rb", + "summary": "Model unit tests for JobApplication. Verifies required field validation and enum value validation (company_name, position_title, status). Verifies salary range ordering validation (salary_max >= salary_min).", + "tags": ["test", "rspec", "model", "validation", "job-application"], + "complexity": "low" + }, + { + "id": "file:spec/requests/applications_spec.rb", + "type": "file", + "name": "applications_spec.rb", + "filePath": "spec/requests/applications_spec.rb", + "summary": "Integration request specs for the Applications API. Tests: CORS header, create unsubmitted (null dateApplied), invalid date format rejection (400), dateApplied preservation on PATCH without date change, dateApplied forced null on unsubmitted status, default stage creation when moving to interviewing, terminal status transition rejection, list filtering/sorting/pagination, archive and restore actions.", + "tags": ["test", "rspec", "request", "integration", "applications", "cors"], + "complexity": "moderate" + }, + { + "id": "file:spec/requests/application_history_spec.rb", + "type": "file", + "name": "application_history_spec.rb", + "filePath": "spec/requests/application_history_spec.rb", + "summary": "Integration request specs for the Application History API. Tests: history recording and snapshot restore flow (creating, patching, viewing history, restoring, verifying description). Validation error for missing sequence. Validation error for sequence < 1. Interview stage UUID preservation during restore.", + "tags": ["test", "rspec", "request", "integration", "history", "snapshot", "restore"], + "complexity": "moderate" + }, + { + "id": "file:spec/requests/interview_stages_spec.rb", + "type": "file", + "name": "interview_stages_spec.rb", + "filePath": "spec/requests/interview_stages_spec.rb", + "summary": "Integration request specs for the Interview Stages API. Tests the full lifecycle: create a stage (201, verifies fields including completedDate), update the stage (200), delete the stage (204).", + "tags": ["test", "rspec", "request", "integration", "interview-stages"], + "complexity": "low" + }, + { + "id": "file:CLAUDE.md", + "type": "document", + "name": "CLAUDE.md", + "filePath": "CLAUDE.md", + "summary": "Stack-specific guidance for AI agents working on rails-api. Covers stack details (Ruby 3.3+, Rails API mode, PostgreSQL ruby_rails schema, RSpec, port 5180), available npm scripts, coding patterns (snake_case DB / camelCase API, string enum values, service objects for mutations, CSV deferred), macOS Ruby version gotcha, Gemfile.lock Bundler metadata, bin/with-ruby helper, and RSpec database isolation warnings.", + "tags": ["documentation", "claude", "guidance", "rails", "patterns"], + "complexity": "low" + }, + { + "id": "file:.env.example", + "type": "config", + "name": ".env.example", + "filePath": ".env.example", + "summary": "Example environment variable template showing the required DATABASE_URL for connecting to the PostgreSQL app_tracker database.", + "tags": ["environment", "configuration", "database", "example"], + "complexity": "low" + } + ], + "edges": [ + { + "source": "file:config.ru", + "target": "file:config/environment.rb", + "type": "imports", + "label": "require_relative" + }, + { + "source": "file:config/environment.rb", + "target": "file:config/application.rb", + "type": "imports", + "label": "require_relative" + }, + { + "source": "file:config/application.rb", + "target": "file:config/boot.rb", + "type": "imports", + "label": "require_relative" + }, + { + "source": "file:config/application.rb", + "target": "file:Gemfile", + "type": "imports", + "label": "Bundler.require" + }, + { + "source": "file:config.ru", + "target": "file:config/routes.rb", + "type": "uses", + "label": "Rails.application" + }, + { + "source": "file:config/routes.rb", + "target": "file:app/controllers/api/applications_controller.rb", + "type": "uses", + "label": "routes to" + }, + { + "source": "file:config/routes.rb", + "target": "file:app/controllers/api/application_history_controller.rb", + "type": "uses", + "label": "routes to" + }, + { + "source": "file:config/routes.rb", + "target": "file:app/controllers/api/health_controller.rb", + "type": "uses", + "label": "routes to" + }, + { + "source": "file:config/routes.rb", + "target": "file:app/controllers/api/interview_stages_controller.rb", + "type": "uses", + "label": "routes to" + }, + { + "source": "file:config/initializers/cors.rb", + "target": "file:Gemfile", + "type": "uses", + "label": "rack-cors gem" + }, + { + "source": "file:config/initializers/active_record_migration_tables.rb", + "target": "file:config/database.yml", + "type": "uses", + "label": "schema isolation" + }, + { + "source": "file:app/models/job_application.rb", + "target": "file:app/models/application_record.rb", + "type": "inherits", + "label": "extends ApplicationRecord" + }, + { + "source": "file:app/models/application_snapshot.rb", + "target": "file:app/models/application_record.rb", + "type": "inherits", + "label": "extends ApplicationRecord" + }, + { + "source": "file:app/models/interview_stage.rb", + "target": "file:app/models/application_record.rb", + "type": "inherits", + "label": "extends ApplicationRecord" + }, + { + "source": "file:app/models/application_snapshot.rb", + "target": "file:app/models/job_application.rb", + "type": "uses", + "label": "belongs_to job_application" + }, + { + "source": "file:app/models/interview_stage.rb", + "target": "file:app/models/job_application.rb", + "type": "uses", + "label": "belongs_to job_application" + }, + { + "source": "file:app/models/job_application.rb", + "target": "file:db/migrate/001_initial_schema.rb", + "type": "uses", + "label": "maps to ruby_rails.applications" + }, + { + "source": "file:app/models/application_snapshot.rb", + "target": "file:db/migrate/001_initial_schema.rb", + "type": "uses", + "label": "maps to ruby_rails.application_snapshots" + }, + { + "source": "file:app/models/interview_stage.rb", + "target": "file:db/migrate/001_initial_schema.rb", + "type": "uses", + "label": "maps to ruby_rails.interview_stages" + }, + { + "source": "file:app/controllers/api/applications_controller.rb", + "target": "file:app/controllers/application_controller.rb", + "type": "inherits", + "label": "extends ApplicationController" + }, + { + "source": "file:app/controllers/api/application_history_controller.rb", + "target": "file:app/controllers/application_controller.rb", + "type": "inherits", + "label": "extends ApplicationController" + }, + { + "source": "file:app/controllers/api/health_controller.rb", + "target": "file:app/controllers/application_controller.rb", + "type": "inherits", + "label": "extends ApplicationController" + }, + { + "source": "file:app/controllers/api/interview_stages_controller.rb", + "target": "file:app/controllers/application_controller.rb", + "type": "inherits", + "label": "extends ApplicationController" + }, + { + "source": "file:app/controllers/api/applications_controller.rb", + "target": "file:app/services/application_service.rb", + "type": "uses", + "label": "delegates business logic" + }, + { + "source": "file:app/controllers/api/applications_controller.rb", + "target": "file:app/serializers/application_serializer.rb", + "type": "uses", + "label": "serializes response" + }, + { + "source": "file:app/controllers/api/applications_controller.rb", + "target": "file:app/models/job_application.rb", + "type": "uses", + "label": "finds record" + }, + { + "source": "file:app/controllers/api/application_history_controller.rb", + "target": "file:app/services/application_restore_service.rb", + "type": "uses", + "label": "delegates restore" + }, + { + "source": "file:app/controllers/api/application_history_controller.rb", + "target": "file:app/serializers/application_serializer.rb", + "type": "uses", + "label": "serializes history entries" + }, + { + "source": "file:app/controllers/api/application_history_controller.rb", + "target": "file:app/models/job_application.rb", + "type": "uses", + "label": "finds record" + }, + { + "source": "file:app/controllers/api/interview_stages_controller.rb", + "target": "file:app/services/interview_stage_service.rb", + "type": "uses", + "label": "delegates stage mutations" + }, + { + "source": "file:app/controllers/api/interview_stages_controller.rb", + "target": "file:app/serializers/application_serializer.rb", + "type": "uses", + "label": "serializes stage response" + }, + { + "source": "file:app/services/application_service.rb", + "target": "file:app/models/job_application.rb", + "type": "uses", + "label": "creates/queries/updates" + }, + { + "source": "file:app/services/application_service.rb", + "target": "file:app/services/application_snapshot_service.rb", + "type": "uses", + "label": "records snapshot after each mutation" + }, + { + "source": "file:app/services/application_service.rb", + "target": "file:app/serializers/application_serializer.rb", + "type": "uses", + "label": "serializes list items" + }, + { + "source": "file:app/services/application_service.rb", + "target": "file:app/services/api_params.rb", + "type": "uses", + "label": "parses request params" + }, + { + "source": "file:app/services/application_snapshot_service.rb", + "target": "file:app/models/application_snapshot.rb", + "type": "uses", + "label": "creates snapshots" + }, + { + "source": "file:app/services/application_snapshot_service.rb", + "target": "file:app/serializers/application_serializer.rb", + "type": "uses", + "label": "serializes snapshot data" + }, + { + "source": "file:app/services/application_restore_service.rb", + "target": "file:app/models/job_application.rb", + "type": "uses", + "label": "updates application state" + }, + { + "source": "file:app/services/application_restore_service.rb", + "target": "file:app/models/application_snapshot.rb", + "type": "uses", + "label": "reads snapshot data" + }, + { + "source": "file:app/services/application_restore_service.rb", + "target": "file:app/services/application_snapshot_service.rb", + "type": "uses", + "label": "records restore snapshot" + }, + { + "source": "file:app/services/application_restore_service.rb", + "target": "file:app/services/api_params.rb", + "type": "uses", + "label": "parses date fields from snapshot" + }, + { + "source": "file:app/services/interview_stage_service.rb", + "target": "file:app/models/interview_stage.rb", + "type": "uses", + "label": "creates/updates/destroys stages" + }, + { + "source": "file:app/services/interview_stage_service.rb", + "target": "file:app/services/application_snapshot_service.rb", + "type": "uses", + "label": "records snapshot after stage mutation" + }, + { + "source": "file:app/services/interview_stage_service.rb", + "target": "file:app/services/api_params.rb", + "type": "uses", + "label": "parses stage params" + }, + { + "source": "file:app/serializers/application_serializer.rb", + "target": "file:app/services/application_service.rb", + "type": "uses", + "label": "references FIELD_LABELS for diff" + }, + { + "source": "file:app/controllers/application_controller.rb", + "target": "file:app/services/api_params.rb", + "type": "uses", + "label": "rescues InvalidDate" + }, + { + "source": "file:spec/rails_helper.rb", + "target": "file:config/environment.rb", + "type": "imports", + "label": "loads test environment" + }, + { + "source": "file:spec/models/job_application_spec.rb", + "target": "file:spec/rails_helper.rb", + "type": "imports", + "label": "require rails_helper" + }, + { + "source": "file:spec/requests/applications_spec.rb", + "target": "file:spec/rails_helper.rb", + "type": "imports", + "label": "require rails_helper" + }, + { + "source": "file:spec/requests/applications_spec.rb", + "target": "file:app/services/application_service.rb", + "type": "uses", + "label": "creates test data" + }, + { + "source": "file:spec/requests/application_history_spec.rb", + "target": "file:spec/rails_helper.rb", + "type": "imports", + "label": "require rails_helper" + }, + { + "source": "file:spec/requests/application_history_spec.rb", + "target": "file:app/services/application_service.rb", + "type": "uses", + "label": "creates test data" + }, + { + "source": "file:spec/requests/interview_stages_spec.rb", + "target": "file:spec/rails_helper.rb", + "type": "imports", + "label": "require rails_helper" + }, + { + "source": "file:spec/requests/interview_stages_spec.rb", + "target": "file:app/services/application_service.rb", + "type": "uses", + "label": "creates test data" + }, + { + "source": "file:spec/models/job_application_spec.rb", + "target": "file:app/models/job_application.rb", + "type": "uses", + "label": "tests model" + } + ], + "layers": [ + { + "id": "layer:boot", + "name": "Boot & Configuration", + "description": "Application startup chain and configuration files. Rack entry point, Rails initializers, environment configs, and the router that wires HTTP paths to controllers.", + "nodeIds": [ + "file:config.ru", + "file:Gemfile", + "file:config/boot.rb", + "file:config/environment.rb", + "file:config/application.rb", + "file:config/routes.rb", + "file:config/database.yml", + "file:config/puma.rb", + "file:config/environments/development.rb", + "file:config/environments/test.rb", + "file:config/environments/production.rb", + "file:config/initializers/cors.rb", + "file:config/initializers/active_record_migration_tables.rb", + "file:Rakefile", + "file:.env.example" + ] + }, + { + "id": "layer:controllers", + "name": "HTTP Controllers", + "description": "Rails controllers that handle HTTP requests and responses. The base ApplicationController provides shared error handling. API controllers delegate business logic to service objects and use the serializer to format JSON responses.", + "nodeIds": [ + "file:app/controllers/application_controller.rb", + "file:app/controllers/api/applications_controller.rb", + "file:app/controllers/api/application_history_controller.rb", + "file:app/controllers/api/health_controller.rb", + "file:app/controllers/api/interview_stages_controller.rb" + ] + }, + { + "id": "layer:services", + "name": "Service Objects", + "description": "Plain Ruby service classes encapsulating business logic. ApplicationService handles list/create/update/archive/restore/delete with transactions and snapshot recording. InterviewStageService manages stage lifecycle. ApplicationSnapshotService records history. ApplicationRestoreService replays historical state. ApiParams handles camelCase-to-snake_case parameter mapping and date validation.", + "nodeIds": [ + "file:app/services/application_service.rb", + "file:app/services/application_snapshot_service.rb", + "file:app/services/application_restore_service.rb", + "file:app/services/interview_stage_service.rb", + "file:app/services/api_params.rb" + ] + }, + { + "id": "layer:models", + "name": "ActiveRecord Models", + "description": "ActiveRecord models representing the three core domain entities. JobApplication is the central model with rich validations and associations. InterviewStage and ApplicationSnapshot are child entities. ApplicationRecord is the shared abstract base.", + "nodeIds": [ + "file:app/models/application_record.rb", + "file:app/models/job_application.rb", + "file:app/models/application_snapshot.rb", + "file:app/models/interview_stage.rb" + ] + }, + { + "id": "layer:serializers", + "name": "Serialization", + "description": "Presentation layer converting ActiveRecord objects to camelCase JSON hashes. ApplicationSerializer provides serialization for applications, interview stages, and history entries with change diffing between snapshots.", + "nodeIds": [ + "file:app/serializers/application_serializer.rb" + ] + }, + { + "id": "layer:database", + "name": "Database Schema", + "description": "PostgreSQL schema definition and migration. Creates the ruby_rails schema with three tables: applications, interview_stages, and application_snapshots. Uses UUIDs as primary keys and JSONB for snapshot storage.", + "nodeIds": [ + "file:db/migrate/001_initial_schema.rb" + ] + }, + { + "id": "layer:tests", + "name": "Tests", + "description": "RSpec test suite covering model validations and full API integration via request specs. Tests use transactional fixtures for database isolation. Covers CRUD, history/restore flows, validation errors, and interview stage lifecycle.", + "nodeIds": [ + "file:spec/spec_helper.rb", + "file:spec/rails_helper.rb", + "file:spec/models/job_application_spec.rb", + "file:spec/requests/applications_spec.rb", + "file:spec/requests/application_history_spec.rb", + "file:spec/requests/interview_stages_spec.rb" + ] + }, + { + "id": "layer:docs", + "name": "Documentation", + "description": "Project documentation and guidance for developers and AI agents working on the stack.", + "nodeIds": [ + "file:CLAUDE.md" + ] + } + ], + "tour": [ + { + "order": 1, + "title": "Application Entry Point", + "description": "The application boots through config.ru (Rack entry point) → config/environment.rb → config/application.rb. The RailsApi::Application class in application.rb configures api_only mode (no views/assets/sessions), sets UTC timezone, and loads only the necessary Rails railties: ActiveModel, ActiveRecord, and ActionController.", + "nodeIds": ["file:config.ru", "file:config/environment.rb", "file:config/application.rb"] + }, + { + "order": 2, + "title": "Routing & API Surface", + "description": "config/routes.rb defines the complete API surface. The main resource is /applications with full CRUD, plus custom member actions (archive, restore) and nested resources for /interview-stages and /history. CSV endpoints are stubbed as 501 Not Implemented. The router maps to controllers in the Api:: namespace.", + "nodeIds": ["file:config/routes.rb"] + }, + { + "order": 3, + "title": "Database Schema & Isolation", + "description": "The initial migration (db/migrate/001_initial_schema.rb) creates the ruby_rails PostgreSQL schema with three tables. The active_record_migration_tables.rb initializer ensures Rails metadata tables (schema_migrations, internal_metadata) are also scoped to the ruby_rails schema, preventing collision with other stacks sharing the app_tracker database.", + "nodeIds": ["file:db/migrate/001_initial_schema.rb", "file:config/initializers/active_record_migration_tables.rb", "file:config/database.yml"] + }, + { + "order": 4, + "title": "Core Domain Model", + "description": "JobApplication is the central domain entity. It defines domain constants (APPLICATION_STATUSES, COMPANY_CATEGORIES, JOB_SOURCES, TERMINAL_STATUSES), has associations to InterviewStage and ApplicationSnapshot with cascade delete, and enforces rich validations: presence/length, enum inclusion, numeric ranges for skills_match (1-5) and salary, salary range ordering, and HTTP URL format.", + "nodeIds": ["file:app/models/job_application.rb", "file:app/models/application_record.rb", "file:app/models/interview_stage.rb", "file:app/models/application_snapshot.rb"] + }, + { + "order": 5, + "title": "HTTP Layer: Controllers", + "description": "ApplicationController is the shared base handling error rescue (RecordInvalid → 400, RecordNotFound → 404, InvalidDate → 400) and camelCase field name formatting in error responses. The Api:: namespace controllers are thin — they call service objects for mutations and the serializer for formatting. ApplicationsController handles the main CRUD flow, ApplicationHistoryController manages history/restore, and InterviewStagesController manages nested stages.", + "nodeIds": ["file:app/controllers/application_controller.rb", "file:app/controllers/api/applications_controller.rb", "file:app/controllers/api/application_history_controller.rb", "file:app/controllers/api/interview_stages_controller.rb"] + }, + { + "order": 6, + "title": "Business Logic: ApplicationService", + "description": "ApplicationService is the heart of the application logic. It handles the complete application lifecycle: list (with filtering by status/category/source/skills_match, sorting, and pagination), create (with default status, date side effects, and auto-creating default interview stages for 'interviewing' status), update (preventing terminal status transitions), archive, restore, and delete. All mutations run in database transactions and call ApplicationSnapshotService to record history.", + "nodeIds": ["file:app/services/application_service.rb"] + }, + { + "order": 7, + "title": "History & Snapshot System", + "description": "Every mutation records a snapshot via ApplicationSnapshotService, which uses pessimistic locking to safely sequence snapshots. ApplicationRestoreService replays a historical state by reading the JSONB snapshot, replacing application attributes, destroying and recreating interview stages with original UUIDs, and recording a new restore snapshot. ApplicationHistoryController computes diffs between consecutive snapshots for the history API response.", + "nodeIds": ["file:app/services/application_snapshot_service.rb", "file:app/services/application_restore_service.rb", "file:app/models/application_snapshot.rb"] + }, + { + "order": 8, + "title": "Parameter Parsing & Serialization", + "description": "ApiParams maps camelCase request body keys to snake_case ActiveRecord attributes via static field maps, and validates date fields (YYYY-MM-DD format only, raising InvalidDate on invalid input). ApplicationSerializer provides the inverse transformation: converting snake_case model attributes to camelCase JSON hashes. It also implements the history diff algorithm comparing consecutive snapshots field-by-field.", + "nodeIds": ["file:app/services/api_params.rb", "file:app/serializers/application_serializer.rb"] + }, + { + "order": 9, + "title": "Test Suite", + "description": "RSpec integration tests cover the full API surface using request specs (preferred over mocks for real HTTP testing). Tests verify CRUD operations, filtering/sorting/pagination, validation errors, CORS headers, archive/restore, history recording and replay, and interview stage lifecycle. Transactional fixtures ensure database isolation. The test suite uses ApplicationService directly to create test data.", + "nodeIds": ["file:spec/rails_helper.rb", "file:spec/requests/applications_spec.rb", "file:spec/requests/application_history_spec.rb", "file:spec/requests/interview_stages_spec.rb", "file:spec/models/job_application_spec.rb"] + } + ] +} diff --git a/rails-api/.understand-anything/meta.json b/rails-api/.understand-anything/meta.json new file mode 100644 index 00000000..4ddc5aef --- /dev/null +++ b/rails-api/.understand-anything/meta.json @@ -0,0 +1,28 @@ +{ + "projectName": "rails-api", + "description": "Rails 8 API-only backend for the job application tracker. Provides RESTful CRUD endpoints for applications, interview stages, and application history snapshots. Uses PostgreSQL with the ruby_rails schema. Runs on port 5180.", + "generatedAt": "2026-05-04T00:00:00.000Z", + "gitHash": "e5054096efec8165e7d4cfe11fc103f77ad2d98f", + "languages": [ + "Ruby", + "SQL" + ], + "frameworks": [ + "Rails 8", + "RSpec", + "ActiveRecord", + "PostgreSQL", + "Puma" + ], + "entryPoints": [ + "config.ru" + ], + "stats": { + "filesAnalyzed": 38, + "totalNodes": 38, + "totalEdges": 55, + "totalLayers": 8, + "tourSteps": 9 + }, + "pluginVersion": "2.5.1" +} diff --git a/react-apollo-ui/.understand-anything/.understandignore b/react-apollo-ui/.understand-anything/.understandignore new file mode 100644 index 00000000..e8bb4ac8 --- /dev/null +++ b/react-apollo-ui/.understand-anything/.understandignore @@ -0,0 +1,6 @@ +# .understandignore — patterns for files/dirs to exclude from analysis +# Built-in defaults (always excluded): node_modules/, .git/, dist/, build/, *.lock + +# Exclude static assets +public/ +src/assets/ diff --git a/react-apollo-ui/.understand-anything/knowledge-graph.json b/react-apollo-ui/.understand-anything/knowledge-graph.json new file mode 100644 index 00000000..b71d7c9e --- /dev/null +++ b/react-apollo-ui/.understand-anything/knowledge-graph.json @@ -0,0 +1,502 @@ +{ + "nodes": [ + { + "id": "file:src/main.tsx", + "type": "file", + "name": "main.tsx", + "filePath": "src/main.tsx", + "summary": "Application entry point. Creates the Apollo Client provider and TanStack Router, wraps the app in StrictMode, ApolloProvider, and RouterProvider, then mounts to the DOM root element.", + "tags": ["entrypoint", "react", "apollo", "router", "typescript"], + "complexity": "moderate" + }, + { + "id": "file:src/apollo/client.ts", + "type": "file", + "name": "client.ts", + "filePath": "src/apollo/client.ts", + "summary": "Configures and exports the Apollo Client singleton. Uses HttpLink pointing to /graphql with InMemoryCache. Sets up typePolicies for Application and InterviewStage entities keyed by id.", + "tags": ["apollo", "graphql", "config", "cache", "typescript"], + "complexity": "moderate" + }, + { + "id": "file:src/graphql/queries.ts", + "type": "file", + "name": "queries.ts", + "filePath": "src/graphql/queries.ts", + "summary": "Defines all GraphQL query documents: GET_APPLICATIONS (paginated list with filters), GET_APPLICATION (single with interview stages), GET_HISTORY (history snapshots). Exports APPLICATION_FIELDS fragment reused across queries and mutations.", + "tags": ["graphql", "queries", "apollo", "typescript"], + "complexity": "moderate" + }, + { + "id": "file:src/graphql/mutations.ts", + "type": "file", + "name": "mutations.ts", + "filePath": "src/graphql/mutations.ts", + "summary": "Defines all GraphQL mutation documents: CREATE/UPDATE/DELETE/ARCHIVE/RESTORE_APPLICATION for job applications, CREATE/UPDATE/DELETE_STAGE for interview stages, and RESTORE_HISTORY for snapshot restoration.", + "tags": ["graphql", "mutations", "apollo", "typescript"], + "complexity": "moderate" + }, + { + "id": "file:src/types/application.ts", + "type": "file", + "name": "application.ts", + "filePath": "src/types/application.ts", + "summary": "Central type definitions for the application domain. Defines ApplicationStatus union type, CompanyCategory and JobSource as const arrays. Exports label maps (STATUS_LABELS, CATEGORY_LABELS, SOURCE_LABELS), TERMINAL_STATUSES, validation constants, and Application/InterviewStage/HistoryEntry interface types.", + "tags": ["types", "domain", "typescript", "constants"], + "complexity": "moderate" + }, + { + "id": "file:src/hooks/useFilters.ts", + "type": "file", + "name": "useFilters.ts", + "filePath": "src/hooks/useFilters.ts", + "summary": "Custom React hook encapsulating filter state management. Returns filters object and setFilters updater, typed using the Filters interface from FilterBar.", + "tags": ["hook", "react", "state", "typescript"], + "complexity": "moderate" + }, + { + "id": "file:src/routes/__root.tsx", + "type": "file", + "name": "__root.tsx", + "filePath": "src/routes/__root.tsx", + "summary": "Root layout route for TanStack Router. Defines the application shell with navigation header (app title, navigation links), renders Outlet for child routes, and includes global theme/nav structure.", + "tags": ["route", "layout", "react", "tanstack-router", "typescript"], + "complexity": "moderate" + }, + { + "id": "file:src/routes/index.tsx", + "type": "file", + "name": "index.tsx", + "filePath": "src/routes/index.tsx", + "summary": "Home/list route at /. Uses Apollo useQuery with GET_APPLICATIONS, integrates useFilters hook, renders FilterBar and application list cards. Supports pagination and displays application status badges. Includes Import button that opens ImportModal.", + "tags": ["route", "react", "apollo", "list", "typescript"], + "complexity": "moderate" + }, + { + "id": "file:src/routes/applications/$id.tsx", + "type": "file", + "name": "$id.tsx", + "filePath": "src/routes/applications/$id.tsx", + "summary": "Application detail/edit route at /applications/:id. Fetches single application with interview stages using GET_APPLICATION. Supports full edit form, interview stage CRUD (add/edit/delete/reorder), archive/restore, delete with confirmation modal, and history viewing with restore capability.", + "tags": ["route", "react", "apollo", "detail", "edit", "typescript"], + "complexity": "moderate" + }, + { + "id": "file:src/routes/applications/new.tsx", + "type": "file", + "name": "new.tsx", + "filePath": "src/routes/applications/new.tsx", + "summary": "New application creation route at /applications/new. Renders creation form using CREATE_APPLICATION mutation. Form fields include company name, position title, status, date applied, job source, salary range, skills match, URLs, and notes. dateApplied disabled when status is unsubmitted; offerDueDate shown only for offer statuses.", + "tags": ["route", "react", "apollo", "form", "create", "typescript"], + "complexity": "moderate" + }, + { + "id": "file:src/routeTree.gen.ts", + "type": "file", + "name": "routeTree.gen.ts", + "filePath": "src/routeTree.gen.ts", + "summary": "Auto-generated TanStack Router route tree. Combines root, index, /applications/new, and /applications/$id routes into a typed route tree exported for use in main.tsx.", + "tags": ["generated", "router", "typescript"], + "complexity": "moderate" + }, + { + "id": "file:src/components/applications/FilterBar.tsx", + "type": "file", + "name": "FilterBar.tsx", + "filePath": "src/components/applications/FilterBar.tsx", + "summary": "Filter bar component for the applications list. Exports Filters interface and FilterBar component with dropdowns for status, company category, job source, skills match minimum, sort field/direction, and include-archived checkbox. Active filters highlighted with GraphQL pink color.", + "tags": ["component", "react", "filter", "typescript"], + "complexity": "moderate" + }, + { + "id": "file:src/components/applications/ImportModal.tsx", + "type": "file", + "name": "ImportModal.tsx", + "filePath": "src/components/applications/ImportModal.tsx", + "summary": "CSV import modal component. Provides file picker for CSV uploads, POSTs to /api/applications/import via fetch (not GraphQL), displays import results (imported count, skipped count, per-row errors), and links to sample CSV template download.", + "tags": ["component", "react", "import", "csv", "modal", "typescript"], + "complexity": "moderate" + }, + { + "id": "file:src/components/ui/Badge.tsx", + "type": "file", + "name": "Badge.tsx", + "filePath": "src/components/ui/Badge.tsx", + "summary": "Status badge UI component. Renders a colored pill badge for ApplicationStatus values using Tailwind CSS color classes per status (sky for applied, amber for interviewing, emerald for offers, red for rejected, etc.).", + "tags": ["component", "ui", "react", "typescript"], + "complexity": "moderate" + }, + { + "id": "file:src/components/ui/Button.tsx", + "type": "file", + "name": "Button.tsx", + "filePath": "src/components/ui/Button.tsx", + "summary": "Reusable Button UI component with primary (GraphQL pink via CSS var), secondary (gray), and danger (red) variants. Supports disabled state, custom className, data-testid, and all standard button types.", + "tags": ["component", "ui", "react", "typescript"], + "complexity": "moderate" + }, + { + "id": "file:src/components/ui/Input.tsx", + "type": "file", + "name": "Input.tsx", + "filePath": "src/components/ui/Input.tsx", + "summary": "Reusable Input field component with optional label, error display, disabled state, and standard HTML input props. Styled with Tailwind and dark mode support.", + "tags": ["component", "ui", "react", "form", "typescript"], + "complexity": "moderate" + }, + { + "id": "file:src/components/ui/Modal.tsx", + "type": "file", + "name": "Modal.tsx", + "filePath": "src/components/ui/Modal.tsx", + "summary": "Confirmation dialog modal component. Renders a centered overlay dialog with title, message, confirm/cancel buttons. Supports isDestructive prop to render red confirm button for dangerous actions like deletion.", + "tags": ["component", "ui", "react", "modal", "typescript"], + "complexity": "moderate" + }, + { + "id": "file:src/components/ui/Select.tsx", + "type": "file", + "name": "Select.tsx", + "filePath": "src/components/ui/Select.tsx", + "summary": "Reusable Select dropdown component with optional label, placeholder option, and disabled state. Accepts SelectOption array and standard change handler.", + "tags": ["component", "ui", "react", "form", "typescript"], + "complexity": "moderate" + }, + { + "id": "file:src/components/ui/Spinner.tsx", + "type": "file", + "name": "Spinner.tsx", + "filePath": "src/components/ui/Spinner.tsx", + "summary": "Loading spinner UI component. Renders an animated spinning circle using Tailwind animate-spin for use during async data loading states.", + "tags": ["component", "ui", "react", "loading", "typescript"], + "complexity": "moderate" + }, + { + "id": "file:src/components/ui/StarRating.tsx", + "type": "file", + "name": "StarRating.tsx", + "filePath": "src/components/ui/StarRating.tsx", + "summary": "Interactive 5-star rating component for skills match. Clicking an already-selected star deselects it (toggle behavior). Stars colored amber when active, gray when inactive. Used for skillsMatch field on application forms.", + "tags": ["component", "ui", "react", "rating", "typescript"], + "complexity": "moderate" + }, + { + "id": "file:src/components/ui/Textarea.tsx", + "type": "file", + "name": "Textarea.tsx", + "filePath": "src/components/ui/Textarea.tsx", + "summary": "Reusable Textarea component with optional label, configurable rows, disabled state, and dark mode Tailwind styling. Vertically resizable.", + "tags": ["component", "ui", "react", "form", "typescript"], + "complexity": "moderate" + }, + { + "id": "file:src/__tests__/ApplicationForm.test.tsx", + "type": "file", + "name": "ApplicationForm.test.tsx", + "filePath": "src/__tests__/ApplicationForm.test.tsx", + "summary": "Unit tests for the new application form route. Tests that form inputs render correctly, dateApplied is disabled when status is wishlist/unsubmitted, and offerDueDate is hidden for non-offer statuses. Uses MockedProvider and TanStack Router memory history.", + "tags": ["test", "vitest", "react-testing-library", "typescript"], + "complexity": "moderate" + }, + { + "id": "file:src/__tests__/FilterBar.test.tsx", + "type": "file", + "name": "FilterBar.test.tsx", + "filePath": "src/__tests__/FilterBar.test.tsx", + "summary": "Unit tests for FilterBar component. Verifies all filter controls render (status, category, source, skills, archived). Uses Vitest with React Testing Library.", + "tags": ["test", "vitest", "react-testing-library", "typescript"], + "complexity": "moderate" + }, + { + "id": "file:src/__tests__/setup.ts", + "type": "file", + "name": "setup.ts", + "filePath": "src/__tests__/setup.ts", + "summary": "Vitest test setup file. Imports @testing-library/jest-dom to extend Vitest matchers with DOM-specific assertions (toBeInTheDocument, etc.).", + "tags": ["test", "setup", "typescript"], + "complexity": "moderate" + }, + { + "id": "file:src/index.css", + "type": "file", + "name": "index.css", + "filePath": "src/index.css", + "summary": "Global CSS entry point. Imports Tailwind CSS v4 base styles and defines custom CSS variables including --gql-pink (GraphQL branding color #E10098) used for primary UI accents.", + "tags": ["css", "styles", "tailwind"], + "complexity": "moderate" + }, + { + "id": "file:vite.config.ts", + "type": "config", + "name": "vite.config.ts", + "filePath": "vite.config.ts", + "summary": "Vite build configuration. Registers React, Tailwind CSS, and TanStack Router Vite plugins. Dev server runs on port 3080 with proxy rules: /graphql → localhost:5080 (yoga-api GraphQL endpoint) and /api → localhost:5080 (REST endpoints for CSV import).", + "tags": ["config", "vite", "build", "typescript"], + "complexity": "moderate" + }, + { + "id": "file:vitest.config.ts", + "type": "config", + "name": "vitest.config.ts", + "filePath": "vitest.config.ts", + "summary": "Vitest configuration for unit testing. Uses jsdom environment, globals mode, and registers the test setup file. Includes @vitejs/plugin-react for JSX transformation in tests.", + "tags": ["config", "test", "vitest", "typescript"], + "complexity": "moderate" + }, + { + "id": "file:tsconfig.json", + "type": "config", + "name": "tsconfig.json", + "filePath": "tsconfig.json", + "summary": "TypeScript configuration for source files. Targets ES2022, uses bundler module resolution, strict mode enabled, noUnusedLocals/Parameters enforced, JSX set to react-jsx.", + "tags": ["config", "typescript"], + "complexity": "moderate" + }, + { + "id": "file:playwright.config.ts", + "type": "config", + "name": "playwright.config.ts", + "filePath": "playwright.config.ts", + "summary": "Playwright E2E test configuration. Points to shared tests/e2e directory. Tests run against localhost:3080 across Chromium, Firefox, and WebKit. Dev server auto-started if not already running.", + "tags": ["config", "playwright", "e2e", "typescript"], + "complexity": "moderate" + }, + { + "id": "file:package.json", + "type": "config", + "name": "package.json", + "filePath": "package.json", + "summary": "npm manifest for react-apollo-ui. Declares React 19, Apollo Client 4, TanStack Router, graphql as production dependencies. Dev tools include Playwright, Tailwind CSS v4, Vitest 4, TypeScript 5.9, ESLint 10.", + "tags": ["config", "npm", "manifest"], + "complexity": "moderate" + }, + { + "id": "file:index.html", + "type": "file", + "name": "index.html", + "filePath": "index.html", + "summary": "Vite HTML entry point. Loads src/main.tsx as a module script. Provides the root div mount point for React.", + "tags": ["html", "entrypoint"], + "complexity": "moderate" + } + ], + "edges": [ + { "source": "file:src/main.tsx", "target": "file:src/apollo/client.ts", "type": "imports", "label": "imports client" }, + { "source": "file:src/main.tsx", "target": "file:src/routeTree.gen.ts", "type": "imports", "label": "imports routeTree" }, + { "source": "file:src/routeTree.gen.ts", "target": "file:src/routes/__root.tsx", "type": "imports", "label": "imports root route" }, + { "source": "file:src/routeTree.gen.ts", "target": "file:src/routes/index.tsx", "type": "imports", "label": "imports index route" }, + { "source": "file:src/routeTree.gen.ts", "target": "file:src/routes/applications/new.tsx", "type": "imports", "label": "imports new route" }, + { "source": "file:src/routeTree.gen.ts", "target": "file:src/routes/applications/$id.tsx", "type": "imports", "label": "imports id route" }, + { "source": "file:src/routes/index.tsx", "target": "file:src/graphql/queries.ts", "type": "imports", "label": "uses GET_APPLICATIONS" }, + { "source": "file:src/routes/index.tsx", "target": "file:src/graphql/mutations.ts", "type": "imports", "label": "uses archive/restore mutations" }, + { "source": "file:src/routes/index.tsx", "target": "file:src/hooks/useFilters.ts", "type": "imports", "label": "uses useFilters" }, + { "source": "file:src/routes/index.tsx", "target": "file:src/components/applications/FilterBar.tsx", "type": "imports", "label": "renders FilterBar" }, + { "source": "file:src/routes/index.tsx", "target": "file:src/components/applications/ImportModal.tsx", "type": "imports", "label": "renders ImportModal" }, + { "source": "file:src/routes/index.tsx", "target": "file:src/components/ui/Badge.tsx", "type": "imports", "label": "renders Badge" }, + { "source": "file:src/routes/index.tsx", "target": "file:src/components/ui/Spinner.tsx", "type": "imports", "label": "renders Spinner" }, + { "source": "file:src/routes/index.tsx", "target": "file:src/components/ui/Button.tsx", "type": "imports", "label": "renders Button" }, + { "source": "file:src/routes/index.tsx", "target": "file:src/types/application.ts", "type": "imports", "label": "uses Application types" }, + { "source": "file:src/routes/applications/$id.tsx", "target": "file:src/graphql/queries.ts", "type": "imports", "label": "uses GET_APPLICATION, GET_HISTORY" }, + { "source": "file:src/routes/applications/$id.tsx", "target": "file:src/graphql/mutations.ts", "type": "imports", "label": "uses CRUD mutations" }, + { "source": "file:src/routes/applications/$id.tsx", "target": "file:src/types/application.ts", "type": "imports", "label": "uses Application types" }, + { "source": "file:src/routes/applications/$id.tsx", "target": "file:src/components/ui/Badge.tsx", "type": "imports", "label": "renders Badge" }, + { "source": "file:src/routes/applications/$id.tsx", "target": "file:src/components/ui/Button.tsx", "type": "imports", "label": "renders Button" }, + { "source": "file:src/routes/applications/$id.tsx", "target": "file:src/components/ui/Input.tsx", "type": "imports", "label": "renders Input" }, + { "source": "file:src/routes/applications/$id.tsx", "target": "file:src/components/ui/Select.tsx", "type": "imports", "label": "renders Select" }, + { "source": "file:src/routes/applications/$id.tsx", "target": "file:src/components/ui/Modal.tsx", "type": "imports", "label": "renders Modal" }, + { "source": "file:src/routes/applications/$id.tsx", "target": "file:src/components/ui/StarRating.tsx", "type": "imports", "label": "renders StarRating" }, + { "source": "file:src/routes/applications/$id.tsx", "target": "file:src/components/ui/Textarea.tsx", "type": "imports", "label": "renders Textarea" }, + { "source": "file:src/routes/applications/$id.tsx", "target": "file:src/components/ui/Spinner.tsx", "type": "imports", "label": "renders Spinner" }, + { "source": "file:src/routes/applications/new.tsx", "target": "file:src/graphql/mutations.ts", "type": "imports", "label": "uses CREATE_APPLICATION" }, + { "source": "file:src/routes/applications/new.tsx", "target": "file:src/types/application.ts", "type": "imports", "label": "uses Application types" }, + { "source": "file:src/routes/applications/new.tsx", "target": "file:src/components/ui/Input.tsx", "type": "imports", "label": "renders Input" }, + { "source": "file:src/routes/applications/new.tsx", "target": "file:src/components/ui/Select.tsx", "type": "imports", "label": "renders Select" }, + { "source": "file:src/routes/applications/new.tsx", "target": "file:src/components/ui/Button.tsx", "type": "imports", "label": "renders Button" }, + { "source": "file:src/routes/applications/new.tsx", "target": "file:src/components/ui/Textarea.tsx", "type": "imports", "label": "renders Textarea" }, + { "source": "file:src/routes/applications/new.tsx", "target": "file:src/components/ui/StarRating.tsx", "type": "imports", "label": "renders StarRating" }, + { "source": "file:src/graphql/mutations.ts", "target": "file:src/graphql/queries.ts", "type": "imports", "label": "re-uses APPLICATION_FIELDS fragment" }, + { "source": "file:src/hooks/useFilters.ts", "target": "file:src/components/applications/FilterBar.tsx", "type": "imports", "label": "uses Filters type" }, + { "source": "file:src/components/applications/FilterBar.tsx", "target": "file:src/types/application.ts", "type": "imports", "label": "uses status/category/source types" }, + { "source": "file:src/components/ui/Badge.tsx", "target": "file:src/types/application.ts", "type": "imports", "label": "uses ApplicationStatus" }, + { "source": "file:src/__tests__/ApplicationForm.test.tsx", "target": "file:src/routes/applications/new.tsx", "type": "tests", "label": "tests new application form" }, + { "source": "file:src/__tests__/ApplicationForm.test.tsx", "target": "file:src/routeTree.gen.ts", "type": "imports", "label": "uses routeTree" }, + { "source": "file:src/__tests__/FilterBar.test.tsx", "target": "file:src/components/applications/FilterBar.tsx", "type": "tests", "label": "tests FilterBar component" }, + { "source": "file:src/main.tsx", "target": "file:index.html", "type": "references", "label": "mounted in index.html" }, + { "source": "file:vite.config.ts", "target": "file:src/main.tsx", "type": "references", "label": "bundles via index.html" }, + { "source": "file:vitest.config.ts", "target": "file:src/__tests__/setup.ts", "type": "references", "label": "uses setup file" }, + { "source": "file:src/main.tsx", "target": "file:src/index.css", "type": "imports", "label": "imports global styles" }, + { "source": "file:vite.config.ts", "target": "file:tsconfig.json", "type": "references", "label": "TypeScript config used at build" }, + { "source": "file:vite.config.ts", "target": "file:playwright.config.ts", "type": "references", "label": "E2E config runs against dev server" }, + { "source": "file:vite.config.ts", "target": "file:package.json", "type": "references", "label": "npm manifest declares all dependencies" } + ], + "layers": [ + { + "id": "layer:entrypoint", + "name": "Application Entrypoint", + "description": "Bootstrap files that wire together all providers and mount the React app. Contains the HTML shell and main.tsx which sets up Apollo Client, TanStack Router, and React root.", + "nodeIds": ["file:src/main.tsx", "file:index.html"] + }, + { + "id": "layer:routing", + "name": "Routing Layer", + "description": "TanStack Router file-based routes and the auto-generated route tree. Each route file is a page-level component mapped to a URL path.", + "nodeIds": [ + "file:src/routes/__root.tsx", + "file:src/routes/index.tsx", + "file:src/routes/applications/$id.tsx", + "file:src/routes/applications/new.tsx", + "file:src/routeTree.gen.ts" + ] + }, + { + "id": "layer:graphql", + "name": "GraphQL Layer", + "description": "Apollo Client setup and all GraphQL operation documents (queries, mutations, fragments). This layer defines the contract with the yoga-api GraphQL backend.", + "nodeIds": [ + "file:src/apollo/client.ts", + "file:src/graphql/queries.ts", + "file:src/graphql/mutations.ts" + ] + }, + { + "id": "layer:domain", + "name": "Domain Types", + "description": "Shared TypeScript type definitions, domain enumerations (ApplicationStatus, CompanyCategory, JobSource), label maps, and validation constants used across the entire application.", + "nodeIds": ["file:src/types/application.ts"] + }, + { + "id": "layer:hooks", + "name": "Custom Hooks", + "description": "Custom React hooks encapsulating reusable stateful logic. Currently contains useFilters for managing list filter state.", + "nodeIds": ["file:src/hooks/useFilters.ts"] + }, + { + "id": "layer:ui-components", + "name": "UI Components", + "description": "Reusable presentational components split into application-specific (FilterBar, ImportModal) and generic UI primitives (Badge, Button, Input, Modal, Select, Spinner, StarRating, Textarea).", + "nodeIds": [ + "file:src/components/applications/FilterBar.tsx", + "file:src/components/applications/ImportModal.tsx", + "file:src/components/ui/Badge.tsx", + "file:src/components/ui/Button.tsx", + "file:src/components/ui/Input.tsx", + "file:src/components/ui/Modal.tsx", + "file:src/components/ui/Select.tsx", + "file:src/components/ui/Spinner.tsx", + "file:src/components/ui/StarRating.tsx", + "file:src/components/ui/Textarea.tsx" + ] + }, + { + "id": "layer:styles", + "name": "Styles", + "description": "Global CSS entry point with Tailwind CSS v4 import and custom CSS variables (--gql-pink GraphQL brand color).", + "nodeIds": ["file:src/index.css"] + }, + { + "id": "layer:config", + "name": "Build & Tool Configuration", + "description": "All project configuration files: Vite build config with dev proxy, Vitest unit test config, TypeScript compiler config, Playwright E2E config, and the npm package manifest.", + "nodeIds": [ + "file:vite.config.ts", + "file:vitest.config.ts", + "file:tsconfig.json", + "file:playwright.config.ts", + "file:package.json" + ] + }, + { + "id": "layer:tests", + "name": "Test Layer", + "description": "Unit and component tests using Vitest + React Testing Library. Includes test setup for jest-dom matchers and tests for the FilterBar component and new application form.", + "nodeIds": [ + "file:src/__tests__/ApplicationForm.test.tsx", + "file:src/__tests__/FilterBar.test.tsx", + "file:src/__tests__/setup.ts" + ] + } + ], + "tour": [ + { + "order": 1, + "title": "Application Bootstrap", + "description": "Start here to understand how the app launches. index.html provides the HTML shell with a #root div. src/main.tsx is the JavaScript entry point — it creates the Apollo Client and TanStack Router instances, then wraps the app in StrictMode, ApolloProvider (for GraphQL access), and RouterProvider (for routing) before mounting to the DOM.", + "nodeIds": ["file:index.html", "file:src/main.tsx"] + }, + { + "order": 2, + "title": "Domain Types & Constants", + "description": "Before diving into components, understand the domain model. src/types/application.ts defines every value type used across the app: ApplicationStatus union, CompanyCategory and JobSource const arrays, human-readable label maps for each, validation length limits, and the Application/InterviewStage/HistoryEntry TypeScript interfaces that mirror the GraphQL schema.", + "nodeIds": ["file:src/types/application.ts"] + }, + { + "order": 3, + "title": "Apollo Client Configuration", + "description": "src/apollo/client.ts creates the singleton Apollo Client. It uses HttpLink pointed at /graphql (proxied to yoga-api at localhost:5080 by Vite) and InMemoryCache with typePolicies ensuring Application and InterviewStage entities are normalized by their id field — enabling automatic cache updates after mutations.", + "nodeIds": ["file:src/apollo/client.ts"] + }, + { + "order": 4, + "title": "GraphQL Operations", + "description": "All GraphQL operation documents live in src/graphql/. queries.ts exports APPLICATION_FIELDS (a reusable fragment), GET_APPLICATIONS (paginated/filtered list), GET_APPLICATION (single with stages), and GET_HISTORY. mutations.ts imports APPLICATION_FIELDS and defines all write operations for applications, stages, and history restore.", + "nodeIds": ["file:src/graphql/queries.ts", "file:src/graphql/mutations.ts"] + }, + { + "order": 5, + "title": "Routing Structure", + "description": "TanStack Router uses file-based routing. src/routeTree.gen.ts is auto-generated and assembles all routes. src/routes/__root.tsx provides the top-level layout (navigation header + Outlet). The three pages are: / (applications list), /applications/new (creation form), and /applications/$id (detail/edit view).", + "nodeIds": ["file:src/routeTree.gen.ts", "file:src/routes/__root.tsx"] + }, + { + "order": 6, + "title": "Applications List Page", + "description": "src/routes/index.tsx is the main page. It uses Apollo useQuery with GET_APPLICATIONS, passes filter variables from useFilters hook, renders FilterBar for filter controls, displays application cards with status Badge, and supports pagination. An Import button opens ImportModal for CSV bulk upload.", + "nodeIds": ["file:src/routes/index.tsx", "file:src/hooks/useFilters.ts", "file:src/components/applications/FilterBar.tsx"] + }, + { + "order": 7, + "title": "Application Detail & Edit", + "description": "src/routes/applications/$id.tsx is the most complex route. It fetches a single application with all interview stages (GET_APPLICATION) and change history (GET_HISTORY). It provides: a full edit form, interview stage management (add/edit/delete/reorder), archive/restore toggle, delete with confirmation dialog, and history timeline with point-in-time restore.", + "nodeIds": ["file:src/routes/applications/$id.tsx"] + }, + { + "order": 8, + "title": "New Application Form", + "description": "src/routes/applications/new.tsx renders the creation form. It fires CREATE_APPLICATION mutation on submit. Conditional UI: dateApplied is disabled when status is 'unsubmitted', and offerDueDate only appears for offer-stage statuses.", + "nodeIds": ["file:src/routes/applications/new.tsx"] + }, + { + "order": 9, + "title": "UI Component Library", + "description": "The src/components/ui/ directory contains the reusable primitive components: Badge (status color pills), Button (primary/secondary/danger variants with --gql-pink CSS var), Input/Select/Textarea (labeled form fields), Modal (confirmation dialogs), Spinner (loading state), and StarRating (interactive 1-5 star skills match input). All use Tailwind CSS with dark mode support.", + "nodeIds": [ + "file:src/components/ui/Badge.tsx", + "file:src/components/ui/Button.tsx", + "file:src/components/ui/Input.tsx", + "file:src/components/ui/Select.tsx", + "file:src/components/ui/Textarea.tsx", + "file:src/components/ui/Modal.tsx", + "file:src/components/ui/Spinner.tsx", + "file:src/components/ui/StarRating.tsx" + ] + }, + { + "order": 10, + "title": "CSV Import Modal", + "description": "src/components/applications/ImportModal.tsx handles CSV bulk import. Notably, it uses the REST endpoint /api/applications/import (not GraphQL) via native fetch — the yoga-api backend exposes this as a REST route for multipart file upload. It shows import results and provides a template CSV download link.", + "nodeIds": ["file:src/components/applications/ImportModal.tsx"] + }, + { + "order": 11, + "title": "Build & Test Configuration", + "description": "vite.config.ts configures the Vite bundler with React, Tailwind, and TanStack Router plugins, plus dev-proxy rules routing /graphql and /api to the backend. vitest.config.ts sets up unit testing with jsdom. playwright.config.ts points to the shared monorepo tests/e2e directory and targets all three browser engines.", + "nodeIds": ["file:vite.config.ts", "file:vitest.config.ts", "file:playwright.config.ts"] + }, + { + "order": 12, + "title": "Tests", + "description": "Unit tests live in src/__tests__/. setup.ts loads @testing-library/jest-dom matchers. FilterBar.test.tsx verifies all filter controls render. ApplicationForm.test.tsx tests the new application form using MockedProvider (Apollo) and TanStack Router memory history.", + "nodeIds": ["file:src/__tests__/setup.ts", "file:src/__tests__/FilterBar.test.tsx", "file:src/__tests__/ApplicationForm.test.tsx"] + } + ] +} diff --git a/react-apollo-ui/.understand-anything/meta.json b/react-apollo-ui/.understand-anything/meta.json new file mode 100644 index 00000000..103b0455 --- /dev/null +++ b/react-apollo-ui/.understand-anything/meta.json @@ -0,0 +1,39 @@ +{ + "version": "2.5.1", + "projectName": "react-apollo-ui", + "description": "React 19 + TypeScript UI using Apollo Client for GraphQL communication with yoga-api backend. Features TanStack Router for file-based routing, Tailwind CSS v4 for styling, and Vite for bundling. Runs on port 3080, proxies /graphql and /api to localhost:5080.", + "gitHash": "e5054096efec8165e7d4cfe11fc103f77ad2d98f", + "analyzedAt": "2026-05-04T00:00:00.000Z", + "languages": [ + "TypeScript", + "TSX", + "CSS" + ], + "frameworks": [ + "React 19", + "Apollo Client 4", + "TanStack Router", + "Tailwind CSS 4", + "Vite 7", + "Vitest 4", + "Playwright" + ], + "stats": { + "totalFiles": 36, + "totalNodes": 31, + "totalEdges": 47, + "totalLayers": 9, + "tourSteps": 12, + "nodeTypes": { + "file": 26, + "config": 5 + }, + "edgeTypes": { + "imports": 39, + "tests": 2, + "references": 6 + } + }, + "entryPoint": "src/main.tsx", + "warnings": [] +} diff --git a/react-ui/.understand-anything/.understandignore b/react-ui/.understand-anything/.understandignore new file mode 100644 index 00000000..e8bb4ac8 --- /dev/null +++ b/react-ui/.understand-anything/.understandignore @@ -0,0 +1,6 @@ +# .understandignore — patterns for files/dirs to exclude from analysis +# Built-in defaults (always excluded): node_modules/, .git/, dist/, build/, *.lock + +# Exclude static assets +public/ +src/assets/ diff --git a/react-ui/.understand-anything/knowledge-graph.json b/react-ui/.understand-anything/knowledge-graph.json new file mode 100644 index 00000000..aeee6852 --- /dev/null +++ b/react-ui/.understand-anything/knowledge-graph.json @@ -0,0 +1,666 @@ +{ + "nodes": [ + { + "id": "src/main.tsx", + "type": "file", + "name": "main.tsx", + "filePath": "src/main.tsx", + "summary": "Application entry point. Mounts the React app with StrictMode and wires in the React Router v7 RouterProvider. Imports global CSS.", + "tags": ["entrypoint", "react", "router"] + }, + { + "id": "src/App.tsx", + "type": "file", + "name": "App.tsx", + "filePath": "src/App.tsx", + "summary": "Root layout component. Renders the persistent Header and an Outlet for nested route content. Provides the shared page shell (dark background).", + "tags": ["layout", "react", "router"] + }, + { + "id": "src/router.tsx", + "type": "file", + "name": "router.tsx", + "filePath": "src/router.tsx", + "summary": "Defines the React Router v7 browser router. Three routes: index (ListPage), /applications/new and /applications/:id (both lazy-load ApplicationEdit). Uses lazy imports for code splitting.", + "tags": ["routing", "react-router", "lazy-loading"] + }, + { + "id": "src/index.css", + "type": "file", + "name": "index.css", + "filePath": "src/index.css", + "summary": "Global stylesheet. Imports Tailwind CSS 4 base styles and defines custom CSS variables for the primary color scale and other design tokens.", + "tags": ["styles", "tailwind", "css"] + }, + { + "id": "src/types/application.ts", + "type": "file", + "name": "application.ts", + "filePath": "src/types/application.ts", + "summary": "Central TypeScript type definitions for the entire application. Defines ApplicationStatus, CompanyCategory, JobSource union types; Application, InterviewStage, and all Create/Update input interfaces; pagination, filter, sort, history, and error response types.", + "tags": ["types", "typescript", "domain-model", "application", "interview-stage", "history"] + }, + { + "id": "src/services/api.ts", + "type": "file", + "name": "api.ts", + "filePath": "src/services/api.ts", + "summary": "HTTP API client layer. Wraps fetch() with a typed handleResponse helper and custom ApiError class. Exposes functions for all backend endpoints: applications CRUD, archive/restore, interview stages CRUD, history fetch and version restore. Uses VITE_API_URL env var with /api proxy fallback.", + "tags": ["api", "http", "fetch", "service", "applications", "interview-stages", "history"] + }, + { + "id": "src/hooks/useApplications.ts", + "type": "file", + "name": "useApplications.ts", + "filePath": "src/hooks/useApplications.ts", + "summary": "Primary data management hook. Encapsulates applications list state (items, total, pagination, loading, error), selected application state, and all CRUD operations for both applications and interview stages. Tracks last-used params to support automatic list refresh after mutations.", + "tags": ["hook", "state-management", "applications", "interview-stages", "crud"] + }, + { + "id": "src/hooks/useFilters.ts", + "type": "file", + "name": "useFilters.ts", + "filePath": "src/hooks/useFilters.ts", + "summary": "Filter state management hook. Manages FilterState (status array, companyCategory, jobSource, skillsMatchMin, includeArchived). Provides individual setter callbacks and a clearFilters reset. Computes hasActiveFilters and activeFilterCount derived values.", + "tags": ["hook", "filters", "state-management"] + }, + { + "id": "src/hooks/useSorting.ts", + "type": "file", + "name": "useSorting.ts", + "filePath": "src/hooks/useSorting.ts", + "summary": "Sort state management hook. Manages SortState (sortBy: dateApplied|companyName|updatedAt, sortDir: asc|desc). Default sort is updatedAt desc. Provides setSortBy, setSortDir, and toggleSortDir.", + "tags": ["hook", "sorting", "state-management"] + }, + { + "id": "src/lib/constants.ts", + "type": "file", + "name": "constants.ts", + "filePath": "src/lib/constants.ts", + "summary": "Application-wide constants. Defines APPLICATION_STATUSES, COMPANY_CATEGORIES, JOB_SOURCES label/value arrays for select options; STATUS_COLORS map for badge styling; DEFAULT_INTERVIEW_STAGES list for quick-add functionality.", + "tags": ["constants", "domain", "ui", "status-colors"] + }, + { + "id": "src/lib/utils.ts", + "type": "file", + "name": "utils.ts", + "filePath": "src/lib/utils.ts", + "summary": "Pure utility functions: cn() for conditional class merging, formatDate() with locale-aware formatting, formatCurrency() and formatSalaryRange() for salary display, getDaysUntil() and isOverdue() for offer deadline tracking, getTodayDate() as ISO date helper.", + "tags": ["utils", "formatting", "dates", "currency", "classnames"] + }, + { + "id": "src/lib/constants.test.ts", + "type": "file", + "name": "constants.test.ts", + "filePath": "src/lib/constants.test.ts", + "summary": "Vitest unit tests for APPLICATION_STATUSES and STATUS_COLORS constants. Verifies ordering, presence of all statuses, and color mappings.", + "tags": ["test", "vitest", "constants"] + }, + { + "id": "src/lib/utils.test.ts", + "type": "file", + "name": "utils.test.ts", + "filePath": "src/lib/utils.test.ts", + "summary": "Vitest unit tests for utility functions in utils.ts. Tests formatting and date calculation logic.", + "tags": ["test", "vitest", "utils"] + }, + { + "id": "src/pages/ListPage.tsx", + "type": "file", + "name": "ListPage.tsx", + "filePath": "src/pages/ListPage.tsx", + "summary": "Main list page. Coordinates useFilters and useSorting hooks, fetches applications with useEffect when filter/sort/page state changes, handles archive/restore/delete actions by calling api directly, and renders FilterBar + ApplicationList with pagination. Does not use useApplications hook - manages its own fetch state.", + "tags": ["page", "react", "applications", "pagination", "filtering", "sorting"] + }, + { + "id": "src/components/applications/ApplicationCard.tsx", + "type": "file", + "name": "ApplicationCard.tsx", + "filePath": "src/components/applications/ApplicationCard.tsx", + "summary": "Card component for a single application in the list. Shows company, position, status badge, interview stage progress, offer deadline with overdue highlighting, category label, and a context menu (archive/delete). Links to the ApplicationEdit detail page.", + "tags": ["component", "application", "card", "list-item"] + }, + { + "id": "src/components/applications/ApplicationDetail.tsx", + "type": "file", + "name": "ApplicationDetail.tsx", + "filePath": "src/components/applications/ApplicationDetail.tsx", + "summary": "Read-only detail view for a single application. Displays all fields (salary range, URLs, category, source, skills match, notes, dates), renders InterviewStageList for stage management, and provides action buttons (edit, archive/restore, delete, history).", + "tags": ["component", "application", "detail", "read-only"] + }, + { + "id": "src/components/applications/ApplicationEdit.tsx", + "type": "file", + "name": "ApplicationEdit.tsx", + "filePath": "src/components/applications/ApplicationEdit.tsx", + "summary": "Combined create/edit page for an application (1082 lines). Loads existing application for edit mode via useApplications, renders ApplicationForm for field editing, manages interview stages inline via InterviewStageList, shows HistoryPanel as a slide-over panel, and handles save/delete/archive/restore navigation.", + "tags": ["component", "page", "application", "edit", "create", "form"] + }, + { + "id": "src/components/applications/ApplicationForm.tsx", + "type": "file", + "name": "ApplicationForm.tsx", + "filePath": "src/components/applications/ApplicationForm.tsx", + "summary": "Controlled form component for application fields. Manages all form fields as local state, validates required fields, and emits CreateApplicationInput or UpdateApplicationInput on submit. Renders Input, Select, UrlFieldInput, Checkbox, and RatingInput primitives.", + "tags": ["component", "form", "application", "controlled-form"] + }, + { + "id": "src/components/applications/ApplicationList.tsx", + "type": "file", + "name": "ApplicationList.tsx", + "filePath": "src/components/applications/ApplicationList.tsx", + "summary": "List container that maps Application[] to ApplicationCard components. Handles loading skeleton, empty state (with or without active filters), and renders Pagination when totalPages > 1.", + "tags": ["component", "list", "applications", "loading-state", "empty-state"] + }, + { + "id": "src/components/applications/FieldDiff.tsx", + "type": "file", + "name": "FieldDiff.tsx", + "filePath": "src/components/applications/FieldDiff.tsx", + "summary": "Displays a visual diff of field changes in a history entry. Shows field label, old value (strikethrough red) and new value (green) for each FieldChange. Handles null, boolean, array, and object value formatting.", + "tags": ["component", "history", "diff", "display"] + }, + { + "id": "src/components/applications/FilterBar.tsx", + "type": "file", + "name": "FilterBar.tsx", + "filePath": "src/components/applications/FilterBar.tsx", + "summary": "Filter and sort control bar. Renders Select dropdowns for status, category, source, and skills match; Checkbox for include-archived; a clear-filters Button with active count; and sort controls (field selector + direction toggle). Shows result count summary.", + "tags": ["component", "filter", "sort", "toolbar"] + }, + { + "id": "src/components/applications/HistoryPanel.tsx", + "type": "file", + "name": "HistoryPanel.tsx", + "filePath": "src/components/applications/HistoryPanel.tsx", + "summary": "Slide-over panel showing application change history. Loads HistoryEntry list from the API, supports expanding entries to see FieldDiff, and allows restoring to a historical version. Closes on Escape key.", + "tags": ["component", "history", "panel", "slide-over", "restore"] + }, + { + "id": "src/components/applications/index.ts", + "type": "file", + "name": "applications/index.ts", + "filePath": "src/components/applications/index.ts", + "summary": "Barrel export for the applications component group: ApplicationCard, ApplicationList, ApplicationForm, ApplicationDetail, ApplicationEdit, FilterBar.", + "tags": ["index", "barrel", "exports"] + }, + { + "id": "src/components/common/Header.tsx", + "type": "file", + "name": "Header.tsx", + "filePath": "src/components/common/Header.tsx", + "summary": "App-wide navigation header. Shows the React logo, app title with stack label, dark/light theme toggle (persisted to localStorage), and an Add Application button that navigates to /applications/new.", + "tags": ["component", "navigation", "header", "dark-mode", "theme"] + }, + { + "id": "src/components/common/index.ts", + "type": "file", + "name": "common/index.ts", + "filePath": "src/components/common/index.ts", + "summary": "Barrel export for common components: Header.", + "tags": ["index", "barrel", "exports"] + }, + { + "id": "src/components/interviews/InlineInterviewStageForm.tsx", + "type": "file", + "name": "InlineInterviewStageForm.tsx", + "filePath": "src/components/interviews/InlineInterviewStageForm.tsx", + "summary": "Inline (non-modal) form for adding or editing an interview stage. Renders directly in the page flow with Input, Checkbox, RatingInput, and TextArea primitives. Used as an alternative to the modal InterviewStageForm.", + "tags": ["component", "form", "interview-stage", "inline"] + }, + { + "id": "src/components/interviews/InterviewStageForm.tsx", + "type": "file", + "name": "InterviewStageForm.tsx", + "filePath": "src/components/interviews/InterviewStageForm.tsx", + "summary": "Modal form for adding or editing an interview stage. Wraps field inputs (name, completion toggle, completion date, performance rating, notes) in a Modal component. Handles create and edit modes.", + "tags": ["component", "form", "interview-stage", "modal"] + }, + { + "id": "src/components/interviews/InterviewStageItem.tsx", + "type": "file", + "name": "InterviewStageItem.tsx", + "filePath": "src/components/interviews/InterviewStageItem.tsx", + "summary": "Single interview stage row. Shows completion checkbox, stage name (strikethrough when done), completion date, performance rating stars, notes indicator, and expand/collapse. Expanded view shows full notes and Edit/Remove buttons with delete confirmation dialog.", + "tags": ["component", "interview-stage", "list-item", "checkbox", "expandable"] + }, + { + "id": "src/components/interviews/InterviewStageList.tsx", + "type": "file", + "name": "InterviewStageList.tsx", + "filePath": "src/components/interviews/InterviewStageList.tsx", + "summary": "Container for interview stage management. Shows progress bar (completed/total), sorted list of InterviewStageItem rows, an Add Default Stages button (when empty), Add Stage button, and an InterviewStageForm modal. Orchestrates add/update/remove and toggle-complete operations.", + "tags": ["component", "interview-stage", "list", "progress", "management"] + }, + { + "id": "src/components/interviews/index.ts", + "type": "file", + "name": "interviews/index.ts", + "filePath": "src/components/interviews/index.ts", + "summary": "Barrel export for interview components: InterviewStageItem, InterviewStageForm, InterviewStageList, InlineInterviewStageForm.", + "tags": ["index", "barrel", "exports"] + }, + { + "id": "src/components/ui/Badge.tsx", + "type": "file", + "name": "Badge.tsx", + "filePath": "src/components/ui/Badge.tsx", + "summary": "Status badge component. Renders a colored pill for an ApplicationStatus value using STATUS_COLORS and APPLICATION_STATUSES label lookup. Supports sm and md sizes.", + "tags": ["component", "ui", "badge", "status"] + }, + { + "id": "src/components/ui/Button.tsx", + "type": "file", + "name": "Button.tsx", + "filePath": "src/components/ui/Button.tsx", + "summary": "Polymorphic button primitive using forwardRef. Four variants (primary, secondary, danger, ghost) and three sizes (sm, md, lg). Extends native button attributes with disabled/focus/transition styles.", + "tags": ["component", "ui", "button", "primitive"] + }, + { + "id": "src/components/ui/Card.tsx", + "type": "file", + "name": "Card.tsx", + "filePath": "src/components/ui/Card.tsx", + "summary": "Card layout primitive with optional hover effect. Exports Card, CardHeader, CardContent, CardFooter sub-components for structured card layouts.", + "tags": ["component", "ui", "card", "layout", "primitive"] + }, + { + "id": "src/components/ui/Checkbox.tsx", + "type": "file", + "name": "Checkbox.tsx", + "filePath": "src/components/ui/Checkbox.tsx", + "summary": "Styled checkbox input with optional label. Wraps native input[type=checkbox] with Tailwind styling and dark mode support.", + "tags": ["component", "ui", "checkbox", "form-input", "primitive"] + }, + { + "id": "src/components/ui/EmptyState.tsx", + "type": "file", + "name": "EmptyState.tsx", + "filePath": "src/components/ui/EmptyState.tsx", + "summary": "Centered empty state display with icon, title, description, and optional action button. Used when lists have no items.", + "tags": ["component", "ui", "empty-state", "feedback"] + }, + { + "id": "src/components/ui/Input.tsx", + "type": "file", + "name": "Input.tsx", + "filePath": "src/components/ui/Input.tsx", + "summary": "Form input primitives. Exports Input (text/date/number/email input with label, error, hint) and TextArea (multi-line input). Both support dark mode and error display.", + "tags": ["component", "ui", "input", "textarea", "form-input", "primitive"] + }, + { + "id": "src/components/ui/Modal.tsx", + "type": "file", + "name": "Modal.tsx", + "filePath": "src/components/ui/Modal.tsx", + "summary": "Modal dialog components. Exports Modal (configurable overlay with title, close button, size variants sm/md/lg/xl) and ConfirmDialog (yes/no confirmation modal with destructive variant). Both handle Escape key and backdrop click.", + "tags": ["component", "ui", "modal", "dialog", "confirm", "primitive"] + }, + { + "id": "src/components/ui/Pagination.tsx", + "type": "file", + "name": "Pagination.tsx", + "filePath": "src/components/ui/Pagination.tsx", + "summary": "Pagination control. Renders prev/next buttons and page number buttons with ellipsis for large page counts. Highlights current page and disables out-of-range navigation.", + "tags": ["component", "ui", "pagination", "navigation"] + }, + { + "id": "src/components/ui/Rating.tsx", + "type": "file", + "name": "Rating.tsx", + "filePath": "src/components/ui/Rating.tsx", + "summary": "Rating components. Exports RatingDisplay (read-only star display with size variants) and RatingInput (interactive 5-star selector with hover states and clear/unset support).", + "tags": ["component", "ui", "rating", "stars", "form-input"] + }, + { + "id": "src/components/ui/Select.tsx", + "type": "file", + "name": "Select.tsx", + "filePath": "src/components/ui/Select.tsx", + "summary": "Styled select dropdown primitive. Accepts options as {value, label}[] array with an optional placeholder option. Supports label, className overrides, and dark mode.", + "tags": ["component", "ui", "select", "dropdown", "form-input", "primitive"] + }, + { + "id": "src/components/ui/UrlFieldInput.tsx", + "type": "file", + "name": "UrlFieldInput.tsx", + "filePath": "src/components/ui/UrlFieldInput.tsx", + "summary": "Specialized URL input with inline open-link button. Renders a text input for URL values with a button that opens the URL in a new tab, only shown when a valid URL is present.", + "tags": ["component", "ui", "url", "input", "form-input"] + }, + { + "id": "src/components/ui/index.ts", + "type": "file", + "name": "ui/index.ts", + "filePath": "src/components/ui/index.ts", + "summary": "Barrel export for all UI primitives: Button, Badge, Card/CardHeader/CardContent/CardFooter, Input/TextArea, Select, Modal/ConfirmDialog, RatingDisplay/RatingInput, Checkbox, EmptyState, Pagination, UrlFieldInput.", + "tags": ["index", "barrel", "exports", "ui-library"] + }, + { + "id": "src/vite-env.d.ts", + "type": "file", + "name": "vite-env.d.ts", + "filePath": "src/vite-env.d.ts", + "summary": "Vite client type declarations. Provides TypeScript types for Vite's import.meta.env and other Vite-specific globals used throughout the codebase.", + "tags": ["types", "vite", "typescript", "declarations"] + }, + { + "id": "vite.config.ts", + "type": "config", + "name": "vite.config.ts", + "filePath": "vite.config.ts", + "summary": "Vite build configuration. Configures React plugin, @ path alias for src/, dev server on port 3010, and API proxy rewriting /api/* to http://localhost:5010/* (Koa API backend).", + "tags": ["config", "vite", "build", "proxy", "dev-server"] + }, + { + "id": "tsconfig.json", + "type": "config", + "name": "tsconfig.json", + "filePath": "tsconfig.json", + "summary": "TypeScript compiler configuration. Strict mode enabled, target ES2020, bundler module resolution, JSX react-jsx, @ path alias, no emit (Vite handles transpilation).", + "tags": ["config", "typescript", "strict"] + }, + { + "id": "package.json", + "type": "config", + "name": "package.json", + "filePath": "package.json", + "summary": "npm package manifest. Declares React 19, React Router 7.13, Tailwind 4, Vite 7, TypeScript 5.9, Vitest 4, Testing Library as dependencies. Scripts: dev, build, lint, preview, test, test:watch.", + "tags": ["config", "npm", "dependencies"] + }, + { + "id": "index.html", + "type": "file", + "name": "index.html", + "filePath": "index.html", + "summary": "Vite HTML entry point. Defines the root div mount target and loads src/main.tsx as an ES module script.", + "tags": ["entrypoint", "html", "vite"] + } + ], + "edges": [ + { "source": "src/main.tsx", "target": "src/router.tsx", "type": "imports", "label": "imports router" }, + { "source": "src/main.tsx", "target": "src/index.css", "type": "imports", "label": "imports styles" }, + { "source": "src/main.tsx", "target": "index.html", "type": "references", "label": "mounts into root div" }, + { "source": "src/router.tsx", "target": "src/App.tsx", "type": "imports", "label": "imports App as root layout" }, + { "source": "src/router.tsx", "target": "src/pages/ListPage.tsx", "type": "imports", "label": "lazy imports ListPage" }, + { "source": "src/router.tsx", "target": "src/components/applications/ApplicationEdit.tsx", "type": "imports", "label": "lazy imports ApplicationEdit" }, + { "source": "src/App.tsx", "target": "src/components/common/Header.tsx", "type": "imports", "label": "renders Header" }, + { "source": "src/pages/ListPage.tsx", "target": "src/hooks/useFilters.ts", "type": "imports", "label": "uses useFilters" }, + { "source": "src/pages/ListPage.tsx", "target": "src/hooks/useSorting.ts", "type": "imports", "label": "uses useSorting" }, + { "source": "src/pages/ListPage.tsx", "target": "src/services/api.ts", "type": "imports", "label": "calls api directly" }, + { "source": "src/pages/ListPage.tsx", "target": "src/components/applications/ApplicationList.tsx", "type": "imports", "label": "renders ApplicationList" }, + { "source": "src/pages/ListPage.tsx", "target": "src/components/applications/FilterBar.tsx", "type": "imports", "label": "renders FilterBar" }, + { "source": "src/pages/ListPage.tsx", "target": "src/types/application.ts", "type": "imports", "label": "imports types" }, + { "source": "src/components/applications/ApplicationEdit.tsx", "target": "src/hooks/useApplications.ts", "type": "imports", "label": "uses useApplications" }, + { "source": "src/components/applications/ApplicationEdit.tsx", "target": "src/components/applications/ApplicationForm.tsx", "type": "imports", "label": "renders ApplicationForm" }, + { "source": "src/components/applications/ApplicationEdit.tsx", "target": "src/components/applications/ApplicationDetail.tsx", "type": "imports", "label": "renders ApplicationDetail" }, + { "source": "src/components/applications/ApplicationEdit.tsx", "target": "src/components/applications/HistoryPanel.tsx", "type": "imports", "label": "renders HistoryPanel" }, + { "source": "src/components/applications/ApplicationEdit.tsx", "target": "src/components/ui/index.ts", "type": "imports", "label": "uses UI primitives" }, + { "source": "src/components/applications/ApplicationEdit.tsx", "target": "src/types/application.ts", "type": "imports", "label": "imports types" }, + { "source": "src/components/applications/ApplicationDetail.tsx", "target": "src/components/interviews/InterviewStageList.tsx", "type": "imports", "label": "renders InterviewStageList" }, + { "source": "src/components/applications/ApplicationDetail.tsx", "target": "src/components/ui/index.ts", "type": "imports", "label": "uses UI primitives" }, + { "source": "src/components/applications/ApplicationDetail.tsx", "target": "src/lib/constants.ts", "type": "imports", "label": "uses constants" }, + { "source": "src/components/applications/ApplicationDetail.tsx", "target": "src/lib/utils.ts", "type": "imports", "label": "uses utils" }, + { "source": "src/components/applications/ApplicationDetail.tsx", "target": "src/types/application.ts", "type": "imports", "label": "imports types" }, + { "source": "src/components/applications/ApplicationForm.tsx", "target": "src/components/ui/index.ts", "type": "imports", "label": "uses UI primitives" }, + { "source": "src/components/applications/ApplicationForm.tsx", "target": "src/lib/constants.ts", "type": "imports", "label": "uses constants" }, + { "source": "src/components/applications/ApplicationForm.tsx", "target": "src/lib/utils.ts", "type": "imports", "label": "uses utils" }, + { "source": "src/components/applications/ApplicationForm.tsx", "target": "src/types/application.ts", "type": "imports", "label": "imports types" }, + { "source": "src/components/applications/ApplicationList.tsx", "target": "src/components/applications/ApplicationCard.tsx", "type": "imports", "label": "renders ApplicationCard" }, + { "source": "src/components/applications/ApplicationList.tsx", "target": "src/components/ui/index.ts", "type": "imports", "label": "uses EmptyState, Pagination" }, + { "source": "src/components/applications/ApplicationList.tsx", "target": "src/types/application.ts", "type": "imports", "label": "imports types" }, + { "source": "src/components/applications/ApplicationCard.tsx", "target": "src/components/ui/index.ts", "type": "imports", "label": "uses Card, Badge, RatingDisplay, ConfirmDialog" }, + { "source": "src/components/applications/ApplicationCard.tsx", "target": "src/lib/constants.ts", "type": "imports", "label": "uses COMPANY_CATEGORIES" }, + { "source": "src/components/applications/ApplicationCard.tsx", "target": "src/lib/utils.ts", "type": "imports", "label": "uses formatDate, getDaysUntil, isOverdue, cn" }, + { "source": "src/components/applications/ApplicationCard.tsx", "target": "src/types/application.ts", "type": "imports", "label": "imports Application type" }, + { "source": "src/components/applications/FilterBar.tsx", "target": "src/components/ui/index.ts", "type": "imports", "label": "uses Button, Select, Checkbox" }, + { "source": "src/components/applications/FilterBar.tsx", "target": "src/lib/constants.ts", "type": "imports", "label": "uses status/category/source arrays" }, + { "source": "src/components/applications/FilterBar.tsx", "target": "src/types/application.ts", "type": "imports", "label": "imports FilterState, SortState types" }, + { "source": "src/components/applications/HistoryPanel.tsx", "target": "src/services/api.ts", "type": "imports", "label": "calls getHistory, restoreToVersion" }, + { "source": "src/components/applications/HistoryPanel.tsx", "target": "src/components/applications/FieldDiff.tsx", "type": "imports", "label": "renders FieldDiff" }, + { "source": "src/components/applications/HistoryPanel.tsx", "target": "src/types/application.ts", "type": "imports", "label": "imports HistoryEntry type" }, + { "source": "src/components/applications/FieldDiff.tsx", "target": "src/types/application.ts", "type": "imports", "label": "imports FieldChange type" }, + { "source": "src/components/applications/index.ts", "target": "src/components/applications/ApplicationCard.tsx", "type": "imports", "label": "re-exports" }, + { "source": "src/components/applications/index.ts", "target": "src/components/applications/ApplicationList.tsx", "type": "imports", "label": "re-exports" }, + { "source": "src/components/applications/index.ts", "target": "src/components/applications/ApplicationForm.tsx", "type": "imports", "label": "re-exports" }, + { "source": "src/components/applications/index.ts", "target": "src/components/applications/ApplicationDetail.tsx", "type": "imports", "label": "re-exports" }, + { "source": "src/components/applications/index.ts", "target": "src/components/applications/ApplicationEdit.tsx", "type": "imports", "label": "re-exports" }, + { "source": "src/components/applications/index.ts", "target": "src/components/applications/FilterBar.tsx", "type": "imports", "label": "re-exports" }, + { "source": "src/components/interviews/InterviewStageList.tsx", "target": "src/components/interviews/InterviewStageItem.tsx", "type": "imports", "label": "renders InterviewStageItem" }, + { "source": "src/components/interviews/InterviewStageList.tsx", "target": "src/components/interviews/InterviewStageForm.tsx", "type": "imports", "label": "renders InterviewStageForm modal" }, + { "source": "src/components/interviews/InterviewStageList.tsx", "target": "src/components/ui/index.ts", "type": "imports", "label": "uses Button" }, + { "source": "src/components/interviews/InterviewStageList.tsx", "target": "src/lib/constants.ts", "type": "imports", "label": "uses DEFAULT_INTERVIEW_STAGES" }, + { "source": "src/components/interviews/InterviewStageList.tsx", "target": "src/types/application.ts", "type": "imports", "label": "imports types" }, + { "source": "src/components/interviews/InterviewStageItem.tsx", "target": "src/components/ui/index.ts", "type": "imports", "label": "uses Button, RatingDisplay, Checkbox, ConfirmDialog" }, + { "source": "src/components/interviews/InterviewStageItem.tsx", "target": "src/lib/utils.ts", "type": "imports", "label": "uses formatDate, getTodayDate, cn" }, + { "source": "src/components/interviews/InterviewStageItem.tsx", "target": "src/types/application.ts", "type": "imports", "label": "imports InterviewStage type" }, + { "source": "src/components/interviews/InterviewStageForm.tsx", "target": "src/components/ui/index.ts", "type": "imports", "label": "uses Button, Input, TextArea, Checkbox, RatingInput, Modal" }, + { "source": "src/components/interviews/InterviewStageForm.tsx", "target": "src/lib/utils.ts", "type": "imports", "label": "uses getTodayDate" }, + { "source": "src/components/interviews/InterviewStageForm.tsx", "target": "src/types/application.ts", "type": "imports", "label": "imports types" }, + { "source": "src/components/interviews/InlineInterviewStageForm.tsx", "target": "src/components/ui/index.ts", "type": "imports", "label": "uses Button, Input, TextArea, Checkbox, RatingInput" }, + { "source": "src/components/interviews/InlineInterviewStageForm.tsx", "target": "src/lib/utils.ts", "type": "imports", "label": "uses getTodayDate" }, + { "source": "src/components/interviews/InlineInterviewStageForm.tsx", "target": "src/types/application.ts", "type": "imports", "label": "imports types" }, + { "source": "src/components/interviews/index.ts", "target": "src/components/interviews/InterviewStageItem.tsx", "type": "imports", "label": "re-exports" }, + { "source": "src/components/interviews/index.ts", "target": "src/components/interviews/InterviewStageForm.tsx", "type": "imports", "label": "re-exports" }, + { "source": "src/components/interviews/index.ts", "target": "src/components/interviews/InterviewStageList.tsx", "type": "imports", "label": "re-exports" }, + { "source": "src/components/interviews/index.ts", "target": "src/components/interviews/InlineInterviewStageForm.tsx", "type": "imports", "label": "re-exports" }, + { "source": "src/components/ui/Badge.tsx", "target": "src/lib/constants.ts", "type": "imports", "label": "uses STATUS_COLORS, APPLICATION_STATUSES" }, + { "source": "src/components/ui/Badge.tsx", "target": "src/types/application.ts", "type": "imports", "label": "imports ApplicationStatus type" }, + { "source": "src/components/ui/Badge.tsx", "target": "src/lib/utils.ts", "type": "imports", "label": "uses cn" }, + { "source": "src/components/ui/Button.tsx", "target": "src/lib/utils.ts", "type": "imports", "label": "uses cn" }, + { "source": "src/components/ui/Card.tsx", "target": "src/lib/utils.ts", "type": "imports", "label": "uses cn" }, + { "source": "src/components/ui/Checkbox.tsx", "target": "src/lib/utils.ts", "type": "imports", "label": "uses cn" }, + { "source": "src/components/ui/Input.tsx", "target": "src/lib/utils.ts", "type": "imports", "label": "uses cn" }, + { "source": "src/components/ui/Modal.tsx", "target": "src/lib/utils.ts", "type": "imports", "label": "uses cn" }, + { "source": "src/components/ui/Rating.tsx", "target": "src/lib/utils.ts", "type": "imports", "label": "uses cn" }, + { "source": "src/components/ui/Select.tsx", "target": "src/lib/utils.ts", "type": "imports", "label": "uses cn" }, + { "source": "src/components/ui/UrlFieldInput.tsx", "target": "src/lib/utils.ts", "type": "imports", "label": "uses cn" }, + { "source": "src/components/ui/index.ts", "target": "src/components/ui/Badge.tsx", "type": "imports", "label": "re-exports" }, + { "source": "src/components/ui/index.ts", "target": "src/components/ui/Button.tsx", "type": "imports", "label": "re-exports" }, + { "source": "src/components/ui/index.ts", "target": "src/components/ui/Card.tsx", "type": "imports", "label": "re-exports" }, + { "source": "src/components/ui/index.ts", "target": "src/components/ui/Checkbox.tsx", "type": "imports", "label": "re-exports" }, + { "source": "src/components/ui/index.ts", "target": "src/components/ui/EmptyState.tsx", "type": "imports", "label": "re-exports" }, + { "source": "src/components/ui/index.ts", "target": "src/components/ui/Input.tsx", "type": "imports", "label": "re-exports" }, + { "source": "src/components/ui/index.ts", "target": "src/components/ui/Modal.tsx", "type": "imports", "label": "re-exports" }, + { "source": "src/components/ui/index.ts", "target": "src/components/ui/Pagination.tsx", "type": "imports", "label": "re-exports" }, + { "source": "src/components/ui/index.ts", "target": "src/components/ui/Rating.tsx", "type": "imports", "label": "re-exports" }, + { "source": "src/components/ui/index.ts", "target": "src/components/ui/Select.tsx", "type": "imports", "label": "re-exports" }, + { "source": "src/components/ui/index.ts", "target": "src/components/ui/UrlFieldInput.tsx", "type": "imports", "label": "re-exports" }, + { "source": "src/hooks/useApplications.ts", "target": "src/services/api.ts", "type": "imports", "label": "calls all api functions" }, + { "source": "src/hooks/useApplications.ts", "target": "src/types/application.ts", "type": "imports", "label": "imports types" }, + { "source": "src/hooks/useFilters.ts", "target": "src/types/application.ts", "type": "imports", "label": "imports FilterState, ApplicationStatus types" }, + { "source": "src/hooks/useSorting.ts", "target": "src/types/application.ts", "type": "imports", "label": "imports SortState type" }, + { "source": "src/lib/constants.ts", "target": "src/types/application.ts", "type": "imports", "label": "imports ApplicationStatus, CompanyCategory, JobSource types" }, + { "source": "src/lib/constants.test.ts", "target": "src/lib/constants.ts", "type": "imports", "label": "tests constants" }, + { "source": "src/lib/utils.test.ts", "target": "src/lib/utils.ts", "type": "imports", "label": "tests utils" }, + { "source": "src/components/common/Header.tsx", "target": "src/components/ui/index.ts", "type": "imports", "label": "uses Button" }, + { "source": "vite.config.ts", "target": "src/main.tsx", "type": "references", "label": "configures entry point" }, + { "source": "src/components/common/index.ts", "target": "src/components/common/Header.tsx", "type": "imports", "label": "re-exports Header" }, + { "source": "src/App.tsx", "target": "src/components/common/index.ts", "type": "imports", "label": "imports from common barrel" }, + { "source": "tsconfig.json", "target": "src/main.tsx", "type": "references", "label": "includes src/ files" }, + { "source": "src/vite-env.d.ts", "target": "vite.config.ts", "type": "references", "label": "provides types for vite env globals" }, + { "source": "package.json", "target": "vite.config.ts", "type": "references", "label": "build scripts invoke vite" } + ], + "layers": [ + { + "id": "layer-entrypoint", + "name": "Entry Points", + "description": "Application bootstrap files: the HTML shell, the main.tsx React mount, the CSS global styles, and the router configuration that defines all routes.", + "nodeIds": ["index.html", "src/main.tsx", "src/index.css", "src/router.tsx"] + }, + { + "id": "layer-config", + "name": "Configuration", + "description": "Build, TypeScript, and package configuration files (vite.config.ts, tsconfig.json, package.json).", + "nodeIds": ["vite.config.ts", "tsconfig.json", "package.json"] + }, + { + "id": "layer-types", + "name": "Domain Types", + "description": "Central TypeScript type definitions for the application domain — all entity types, API input/output shapes, UI state types. The single source of truth for the data model.", + "nodeIds": ["src/types/application.ts"] + }, + { + "id": "layer-api", + "name": "API Service Layer", + "description": "HTTP client abstraction for the backend REST API. Encapsulates fetch() calls, error handling, and query string building behind typed function interfaces.", + "nodeIds": ["src/services/api.ts"] + }, + { + "id": "layer-lib", + "name": "Utilities & Constants", + "description": "Pure utility functions (formatting, date math, classname merging) and application-wide constants (status lists, color maps, default values).", + "nodeIds": ["src/lib/utils.ts", "src/lib/constants.ts", "src/lib/constants.test.ts", "src/lib/utils.test.ts"] + }, + { + "id": "layer-hooks", + "name": "Custom Hooks", + "description": "React custom hooks managing application state: useApplications for data fetching/mutation, useFilters for filter state, useSorting for sort state.", + "nodeIds": ["src/hooks/useApplications.ts", "src/hooks/useFilters.ts", "src/hooks/useSorting.ts"] + }, + { + "id": "layer-ui-primitives", + "name": "UI Primitives", + "description": "Reusable, stateless UI building blocks (design system components): Button, Badge, Card, Input, Select, Modal, Rating, Checkbox, Pagination, EmptyState, UrlFieldInput. All consume only lib utilities.", + "nodeIds": [ + "src/components/ui/index.ts", + "src/components/ui/Button.tsx", + "src/components/ui/Badge.tsx", + "src/components/ui/Card.tsx", + "src/components/ui/Checkbox.tsx", + "src/components/ui/EmptyState.tsx", + "src/components/ui/Input.tsx", + "src/components/ui/Modal.tsx", + "src/components/ui/Pagination.tsx", + "src/components/ui/Rating.tsx", + "src/components/ui/Select.tsx", + "src/components/ui/UrlFieldInput.tsx" + ] + }, + { + "id": "layer-interview-components", + "name": "Interview Stage Components", + "description": "Feature components for managing interview pipeline stages within an application: list view with progress bar, individual stage items, add/edit forms (modal and inline variants).", + "nodeIds": [ + "src/components/interviews/index.ts", + "src/components/interviews/InterviewStageList.tsx", + "src/components/interviews/InterviewStageItem.tsx", + "src/components/interviews/InterviewStageForm.tsx", + "src/components/interviews/InlineInterviewStageForm.tsx" + ] + }, + { + "id": "layer-application-components", + "name": "Application Feature Components", + "description": "Feature components for managing job applications: card (list item), list container, detail view, create/edit form, filter/sort bar, history panel with field diff display.", + "nodeIds": [ + "src/components/applications/index.ts", + "src/components/applications/ApplicationCard.tsx", + "src/components/applications/ApplicationList.tsx", + "src/components/applications/ApplicationDetail.tsx", + "src/components/applications/ApplicationForm.tsx", + "src/components/applications/ApplicationEdit.tsx", + "src/components/applications/FilterBar.tsx", + "src/components/applications/HistoryPanel.tsx", + "src/components/applications/FieldDiff.tsx" + ] + }, + { + "id": "layer-common-components", + "name": "Common Components", + "description": "Shared layout components used across all pages: the persistent Header with navigation, dark mode toggle, and add-application button.", + "nodeIds": ["src/components/common/Header.tsx", "src/components/common/index.ts", "src/vite-env.d.ts"] + }, + { + "id": "layer-layout", + "name": "Layout & App Shell", + "description": "Top-level layout component (App.tsx) that provides the page shell, renders the Header, and hosts the router Outlet.", + "nodeIds": ["src/App.tsx"] + }, + { + "id": "layer-pages", + "name": "Pages", + "description": "Route-level page components. ListPage is the main application list view. ApplicationEdit (lazy-loaded) handles both create and edit workflows.", + "nodeIds": ["src/pages/ListPage.tsx"] + } + ], + "tour": [ + { + "id": "tour-step-1", + "title": "Entry Point", + "description": "Start with src/main.tsx — the React app bootstrap. It mounts the app with React.StrictMode and connects the React Router v7 RouterProvider. Then check index.html to see the div#root mount target.", + "nodeIds": ["src/main.tsx", "index.html"], + "order": 1 + }, + { + "id": "tour-step-2", + "title": "Routing", + "description": "Open src/router.tsx to see how the app routes are defined. There are three routes: the root (/) renders ListPage, while /applications/new and /applications/:id both lazy-load ApplicationEdit. This code-splitting approach keeps the initial bundle lean.", + "nodeIds": ["src/router.tsx"], + "order": 2 + }, + { + "id": "tour-step-3", + "title": "Domain Types", + "description": "Read src/types/application.ts next — it's the central data model. Every entity (Application, InterviewStage, HistoryEntry), every union type (ApplicationStatus, CompanyCategory, JobSource), and every API input/output shape is defined here. Understanding this file unlocks the rest of the codebase.", + "nodeIds": ["src/types/application.ts"], + "order": 3 + }, + { + "id": "tour-step-4", + "title": "API Service Layer", + "description": "Examine src/services/api.ts to see how the frontend communicates with the backend. The ApiError class and handleResponse helper wrap all fetch calls. The proxy in vite.config.ts rewrites /api/* to the Koa backend on port 5010.", + "nodeIds": ["src/services/api.ts", "vite.config.ts"], + "order": 4 + }, + { + "id": "tour-step-5", + "title": "Custom Hooks", + "description": "Look at the three custom hooks. useApplications (src/hooks/useApplications.ts) is the primary state manager — it encapsulates the full application CRUD lifecycle, tracks lastParams for automatic list refresh, and manages selectedApplication. useFilters and useSorting are lightweight state containers for their respective UI controls.", + "nodeIds": ["src/hooks/useApplications.ts", "src/hooks/useFilters.ts", "src/hooks/useSorting.ts"], + "order": 5 + }, + { + "id": "tour-step-6", + "title": "Utilities & Constants", + "description": "src/lib/constants.ts defines all the label/value arrays for dropdowns and the STATUS_COLORS map used by Badge. src/lib/utils.ts has pure functions: cn() for Tailwind class merging, date formatters, salary formatters, and offer-deadline helpers. These are tested in the .test.ts siblings.", + "nodeIds": ["src/lib/constants.ts", "src/lib/utils.ts"], + "order": 6 + }, + { + "id": "tour-step-7", + "title": "UI Primitives", + "description": "The src/components/ui/ directory is the design system. Button, Card, Input, Select, Modal, Rating, Badge, Checkbox, Pagination, EmptyState, and UrlFieldInput are all small, self-contained primitives. Badge is unique in that it reads APPLICATION_STATUSES and STATUS_COLORS — it's domain-aware.", + "nodeIds": ["src/components/ui/Button.tsx", "src/components/ui/Badge.tsx", "src/components/ui/Modal.tsx", "src/components/ui/Rating.tsx"], + "order": 7 + }, + { + "id": "tour-step-8", + "title": "Main List Page", + "description": "src/pages/ListPage.tsx is the default view. It wires useFilters + useSorting to a useEffect that re-fetches applications whenever filter/sort/page state changes. It calls api directly rather than using useApplications, rendering FilterBar and ApplicationList.", + "nodeIds": ["src/pages/ListPage.tsx"], + "order": 8 + }, + { + "id": "tour-step-9", + "title": "Application Components", + "description": "ApplicationList maps Application[] to ApplicationCard components, handling empty state and pagination. ApplicationCard shows company, status badge, stage progress, and offer deadline with a context menu. ApplicationForm is the controlled form for creating/editing all application fields.", + "nodeIds": ["src/components/applications/ApplicationList.tsx", "src/components/applications/ApplicationCard.tsx", "src/components/applications/ApplicationForm.tsx"], + "order": 9 + }, + { + "id": "tour-step-10", + "title": "Application Edit Page", + "description": "ApplicationEdit (src/components/applications/ApplicationEdit.tsx) is the largest file (1082 lines). It handles both create and edit flows, uses useApplications for data, embeds ApplicationForm and ApplicationDetail, and triggers the HistoryPanel slide-over.", + "nodeIds": ["src/components/applications/ApplicationEdit.tsx", "src/components/applications/ApplicationDetail.tsx", "src/components/applications/HistoryPanel.tsx", "src/components/applications/FieldDiff.tsx"], + "order": 10 + }, + { + "id": "tour-step-11", + "title": "Interview Stage Management", + "description": "InterviewStageList (src/components/interviews/InterviewStageList.tsx) orchestrates the interview pipeline UI: a progress bar, sorted list of InterviewStageItem rows, and an Add Default Stages quick-action. InterviewStageForm is the modal editor; InlineInterviewStageForm is its inline alternative.", + "nodeIds": ["src/components/interviews/InterviewStageList.tsx", "src/components/interviews/InterviewStageItem.tsx", "src/components/interviews/InterviewStageForm.tsx"], + "order": 11 + } + ] +} diff --git a/react-ui/.understand-anything/meta.json b/react-ui/.understand-anything/meta.json new file mode 100644 index 00000000..7f15a347 --- /dev/null +++ b/react-ui/.understand-anything/meta.json @@ -0,0 +1,6 @@ +{ + "lastAnalyzedAt": "2026-05-04T00:00:00.000Z", + "gitCommitHash": "e5054096efec8165e7d4cfe11fc103f77ad2d98f", + "version": "1.0.0", + "analyzedFiles": 47 +} diff --git a/skills-lock.json b/skills-lock.json index a23c2afb..a6fe0732 100644 --- a/skills-lock.json +++ b/skills-lock.json @@ -27,7 +27,7 @@ "source": "whatifwedigdeeper/agent-skills", "sourceType": "github", "skillPath": "skills/peer-review/SKILL.md", - "computedHash": "593645f61fd1eb047e4f37b06162a97c77e968e3297db5f1030ebf79f7743b6c" + "computedHash": "6b299ec6aa4e3145f0484eb62ad314cdd848f2a40aff4ee4f28ea8bea92e6776" }, "playwright-cli": { "source": "microsoft/playwright-cli", diff --git a/specs/029-understand-codebase-graphs/spec.md b/specs/029-understand-codebase-graphs/spec.md new file mode 100644 index 00000000..5d68e544 --- /dev/null +++ b/specs/029-understand-codebase-graphs/spec.md @@ -0,0 +1,63 @@ +# Spec 029 — Codebase Knowledge Graphs + +**Status:** Complete (20 of 21 stacks; `angular-spring-ui` deferred to a follow-up) +**Branch:** feat/understand-codebase-graphs + +## Goal + +Produce a navigable knowledge graph for each implementation stack analyzed in this iteration, using the `understand-anything` plugin. Each analyzed stack gets its own `.understand-anything/knowledge-graph.json` (per-directory working unit). `angular-spring-ui` is intentionally deferred to a follow-up — see the Stacks table below. + +## Strategy + +- **Per-directory graphs** are the primary working unit — each stack directory is self-contained and viewable independently via the dashboard. +- **No paired or unified merges** — the PoC showed merged graphs are too overwhelming to navigate and don't clearly identify API vs UI stacks in the dashboard. + +## Stacks + +| Stack directory | Type | Status | +|---|---|---| +| `lambda-api` | API (DynamoDB/Hono/Lambda) | ✅ Done | +| `lambda-react-ui` | UI (React 19/Vite/Zustand) | ✅ Done | +| `api` | API (Express/Prisma/PostgreSQL) | ✅ Done | +| `koa-api` | API (Koa/PostgreSQL) | ✅ Done | +| `react-ui` | UI (React/Vite) | ✅ Done | +| `vue-ui` | UI (Vue 3) | ✅ Done | +| `nuxt-api` | Full-stack (Nuxt/Drizzle) | ✅ Done | +| `hono-api` | API (Hono/Drizzle) | ✅ Done | +| `svelte-ui` | UI (SvelteKit) | ✅ Done | +| `nest-api` | API (NestJS/Drizzle) | ✅ Done | +| `tanstack-ui` | UI (TanStack Router/Query) | ✅ Done | +| `tanstack-start-ui` | UI (TanStack Start SSR) | ✅ Done | +| `fastapi` | API (Python/FastAPI/asyncpg) | ✅ Done | +| `angular-ui` | UI (Angular 21) | ✅ Done | +| `angular-spring-ui` | UI (Angular 21/Spring Boot) | ⬜ Not analyzed | +| `go-api` | API (Go/Gin/sqlc) | ✅ Done | +| `spring-api` | API (Java/Spring Boot/Flyway) | ✅ Done | +| `yoga-api` | API (GraphQL Yoga/Prisma) | ✅ Done | +| `react-apollo-ui` | UI (React/Apollo Client) | ✅ Done | +| `nest-history-api` | API (NestJS gRPC microservice) | ✅ Done | +| `rails-api` | API (Ruby on Rails) | ✅ Done | +| Monorepo root (unified merge) | All 20 stacks merged | ❌ Removed — too overwhelming, dashboard doesn't distinguish API vs UI | + +## Deliverables + +1. `.understand-anything/knowledge-graph.json` in each stack directory listed above. +2. A `## Codebase Knowledge Graphs` section in the root `README.md` explaining what the graphs are, how to view them (dashboard command), and listing all stacks. + +## Dashboard Usage + +To view any stack's graph: + +```bash +cd /packages/dashboard +GRAPH_DIR=/path/to/application-tracker/ npx vite --host 127.0.0.1 +``` + +The Vite server prints a tokenized URL — use the full URL including `?token=`. + +## Notes + +- Plugin root: `~/.claude/plugins/cache/understand-anything/understand-anything/` +- `python` → use `python3` on this system +- Dashboard requires `dangerouslyDisableSandbox: true` to kill with `kill ` +- After each stack, update the Status column in this spec to ✅ Done diff --git a/spring-api/.understand-anything/.understandignore b/spring-api/.understand-anything/.understandignore new file mode 100644 index 00000000..dc441955 --- /dev/null +++ b/spring-api/.understand-anything/.understandignore @@ -0,0 +1,8 @@ +# .understandignore — patterns for files/dirs to exclude from analysis +# Built-in defaults (always excluded): node_modules/, .git/, dist/, build/, *.lock + +# Exclude Gradle wrapper binaries and generated build dirs +gradle/ +gradlew +gradlew.bat +.gradle/ diff --git a/spring-api/.understand-anything/knowledge-graph.json b/spring-api/.understand-anything/knowledge-graph.json new file mode 100644 index 00000000..1bed09c5 --- /dev/null +++ b/spring-api/.understand-anything/knowledge-graph.json @@ -0,0 +1,1117 @@ +{ + "nodes": [ + { + "id": "file:src/main/java/com/example/tracker/TrackerApplication.java", + "type": "file", + "name": "TrackerApplication", + "filePath": "src/main/java/com/example/tracker/TrackerApplication.java", + "summary": "Spring Boot application entry point. Contains the main() method annotated with @SpringBootApplication that bootstraps the Spring context via SpringApplication.run().", + "tags": [ + "entry-point", + "spring-boot", + "java" + ], + "complexity": "moderate" + }, + { + "id": "file:src/main/java/com/example/tracker/config/JacksonConfig.java", + "type": "file", + "name": "JacksonConfig", + "filePath": "src/main/java/com/example/tracker/config/JacksonConfig.java", + "summary": "Spring @Configuration bean that defines a custom ObjectMapper. Registers JavaTimeModule, disables WRITE_DATES_AS_TIMESTAMPS so LocalDate/OffsetDateTime serialize as ISO strings, and uses LOWER_CAMEL_CASE naming.", + "tags": [ + "config", + "jackson", + "serialization", + "spring" + ], + "complexity": "moderate" + }, + { + "id": "file:src/main/java/com/example/tracker/config/WebConfig.java", + "type": "file", + "name": "WebConfig", + "filePath": "src/main/java/com/example/tracker/config/WebConfig.java", + "summary": "Spring @Configuration implementing WebMvcConfigurer. Configures CORS for all /api/** endpoints, allowing the Angular frontend at http://localhost:3070 to call GET, POST, PATCH, PUT, DELETE, OPTIONS.", + "tags": [ + "config", + "cors", + "spring-mvc", + "java" + ], + "complexity": "moderate" + }, + { + "id": "file:src/main/java/com/example/tracker/controller/ApplicationController.java", + "type": "file", + "name": "ApplicationController", + "filePath": "src/main/java/com/example/tracker/controller/ApplicationController.java", + "summary": "REST controller mapped to /api/applications. Exposes endpoints: GET (list with filters/pagination), GET /{id}, POST (create), PATCH /{id} (update), DELETE /{id}, PATCH /{id}/archive, PATCH /{id}/restore, POST /{id}/interview-stages, PATCH /{id}/interview-stages/{stageId}, DELETE /{id}/interview-stages/{stageId}, GET /{id}/history, POST /{id}/history/{snapshotId}/restore, GET /export/csv, GET /sample-csv, POST /import/csv.", + "tags": [ + "controller", + "rest", + "api", + "spring-mvc", + "java" + ], + "complexity": "moderate" + }, + { + "id": "file:src/main/java/com/example/tracker/controller/GlobalExceptionHandler.java", + "type": "file", + "name": "GlobalExceptionHandler", + "filePath": "src/main/java/com/example/tracker/controller/GlobalExceptionHandler.java", + "summary": "@RestControllerAdvice that centralizes error handling. Maps EntityNotFoundException\u2192404, MethodArgumentNotValidException\u2192400 with field errors, IllegalArgumentException\u2192400, HttpMessageNotReadableException\u2192400 with validation_error code, and RuntimeException\u2192500.", + "tags": [ + "exception-handling", + "spring-mvc", + "rest", + "java" + ], + "complexity": "moderate" + }, + { + "id": "file:src/main/java/com/example/tracker/service/ApplicationService.java", + "type": "service", + "name": "ApplicationService", + "filePath": "src/main/java/com/example/tracker/service/ApplicationService.java", + "summary": "@Service @Transactional class implementing all application business logic: CRUD, archive/restore, interview stage management, change history with JSON snapshots, CSV export and import (with per-row TransactionTemplate for batch isolation), and filtering via JPA Specifications. Automatically creates 6 default interview stages on transition to INTERVIEWING status.", + "tags": [ + "service", + "business-logic", + "transactional", + "csv", + "history", + "spring", + "java" + ], + "complexity": "moderate" + }, + { + "id": "file:src/main/java/com/example/tracker/service/ApplicationSpecifications.java", + "type": "file", + "name": "ApplicationSpecifications", + "filePath": "src/main/java/com/example/tracker/service/ApplicationSpecifications.java", + "summary": "Utility class providing JPA Specification factory methods for multi-criteria filtering: hasStatus, hasCategory, hasJobSource, hasMinSkillsMatch, isArchived. Enums are parsed via fromValue() with graceful fallback on unknown values.", + "tags": [ + "jpa", + "specification", + "filtering", + "java" + ], + "complexity": "moderate" + }, + { + "id": "file:src/main/java/com/example/tracker/dto/ApplicationRequest.java", + "type": "file", + "name": "ApplicationRequest", + "filePath": "src/main/java/com/example/tracker/dto/ApplicationRequest.java", + "summary": "Java record DTO for creating/updating applications. Fields include companyName (@NotBlank @Size(max=200)), positionTitle, status (string), dateApplied, URL fields, companyCategory, skillsMatch (@Min(1) @Max(10)), jobSource, salary range, coverLetterRequired, offerDueDate, specialRequirements, notes.", + "tags": [ + "dto", + "request", + "validation", + "java", + "record" + ], + "complexity": "moderate" + }, + { + "id": "file:src/main/java/com/example/tracker/dto/ApplicationResponse.java", + "type": "file", + "name": "ApplicationResponse", + "filePath": "src/main/java/com/example/tracker/dto/ApplicationResponse.java", + "summary": "Java record DTO for API responses. Contains all application fields including id (UUID), isArchived, interviewStages (List), createdAt and updatedAt (OffsetDateTime).", + "tags": [ + "dto", + "response", + "java", + "record" + ], + "complexity": "moderate" + }, + { + "id": "file:src/main/java/com/example/tracker/dto/HistoryDiff.java", + "type": "file", + "name": "HistoryDiff", + "filePath": "src/main/java/com/example/tracker/dto/HistoryDiff.java", + "summary": "Java record representing a single field-level diff between two application snapshots: field (camelCase key), label (human-readable), oldValue, newValue.", + "tags": [ + "dto", + "history", + "diff", + "java", + "record" + ], + "complexity": "moderate" + }, + { + "id": "file:src/main/java/com/example/tracker/dto/HistoryEntry.java", + "type": "file", + "name": "HistoryEntry", + "filePath": "src/main/java/com/example/tracker/dto/HistoryEntry.java", + "summary": "Java record representing one history entry (snapshot): id, sequence number, description string, list of HistoryDiff changes, and createdAt timestamp.", + "tags": [ + "dto", + "history", + "java", + "record" + ], + "complexity": "moderate" + }, + { + "id": "file:src/main/java/com/example/tracker/dto/ImportError.java", + "type": "file", + "name": "ImportError", + "filePath": "src/main/java/com/example/tracker/dto/ImportError.java", + "summary": "Java record DTO describing a single CSV import failure: row number and error message string.", + "tags": [ + "dto", + "import", + "csv", + "java", + "record" + ], + "complexity": "moderate" + }, + { + "id": "file:src/main/java/com/example/tracker/dto/ImportResult.java", + "type": "file", + "name": "ImportResult", + "filePath": "src/main/java/com/example/tracker/dto/ImportResult.java", + "summary": "Java record DTO summarizing a CSV import operation: number imported, number skipped (duplicate URLs), and list of ImportError objects for failed rows.", + "tags": [ + "dto", + "import", + "csv", + "java", + "record" + ], + "complexity": "moderate" + }, + { + "id": "file:src/main/java/com/example/tracker/dto/InterviewStageRequest.java", + "type": "file", + "name": "InterviewStageRequest", + "filePath": "src/main/java/com/example/tracker/dto/InterviewStageRequest.java", + "summary": "Java record DTO for creating/updating an interview stage: name (@NotBlank @Size(max=200)), order, isCompleted, completedDate (String, parsed to LocalDate), notes, performanceRating (@Min(1) @Max(5)).", + "tags": [ + "dto", + "request", + "interview-stage", + "validation", + "java", + "record" + ], + "complexity": "moderate" + }, + { + "id": "file:src/main/java/com/example/tracker/dto/InterviewStageResponse.java", + "type": "file", + "name": "InterviewStageResponse", + "filePath": "src/main/java/com/example/tracker/dto/InterviewStageResponse.java", + "summary": "Java record DTO for interview stage API responses: id, applicationId, name, order, isCompleted, completedDate (LocalDate), notes, performanceRating.", + "tags": [ + "dto", + "response", + "interview-stage", + "java", + "record" + ], + "complexity": "moderate" + }, + { + "id": "file:src/main/java/com/example/tracker/dto/PaginatedResponse.java", + "type": "file", + "name": "PaginatedResponse", + "filePath": "src/main/java/com/example/tracker/dto/PaginatedResponse.java", + "summary": "Generic Java record DTO for paginated list responses: items (List), page number, limit, and total element count.", + "tags": [ + "dto", + "pagination", + "generic", + "java", + "record" + ], + "complexity": "moderate" + }, + { + "id": "file:src/main/java/com/example/tracker/entity/Application.java", + "type": "file", + "name": "Application", + "filePath": "src/main/java/com/example/tracker/entity/Application.java", + "summary": "@Entity mapped to java_spring.applications table. UUID primary key with @GeneratedValue(UUID), custom PostgreSQL enum types via @Type for status/companyCategory/jobSource, @OneToMany cascade to InterviewStage ordered by stageOrder. @PrePersist/@PreUpdate manage createdAt/updatedAt timestamps.", + "tags": [ + "entity", + "jpa", + "application", + "java", + "hibernate" + ], + "complexity": "moderate" + }, + { + "id": "file:src/main/java/com/example/tracker/entity/ApplicationSnapshot.java", + "type": "file", + "name": "ApplicationSnapshot", + "filePath": "src/main/java/com/example/tracker/entity/ApplicationSnapshot.java", + "summary": "@Entity mapped to java_spring.application_snapshots. Stores point-in-time JSON snapshots of Application state using @JdbcTypeCode(SqlTypes.JSON) for the JSONB data column. Has sequenceNumber (integer) and description (VARCHAR 500).", + "tags": [ + "entity", + "jpa", + "snapshot", + "history", + "jsonb", + "java" + ], + "complexity": "moderate" + }, + { + "id": "file:src/main/java/com/example/tracker/entity/ApplicationStatus.java", + "type": "file", + "name": "ApplicationStatus", + "filePath": "src/main/java/com/example/tracker/entity/ApplicationStatus.java", + "summary": "Java enum for application lifecycle states: UNSUBMITTED, APPLIED, INTERVIEWING, GIVEN_OFFER, ACCEPTED_OFFER, DECLINED_OFFER, REJECTED, NO_OFFER. Each value maps to a lowercase/space PostgreSQL enum string via getValue() and fromValue().", + "tags": [ + "enum", + "status", + "java" + ], + "complexity": "moderate" + }, + { + "id": "file:src/main/java/com/example/tracker/entity/ApplicationStatusUserType.java", + "type": "file", + "name": "ApplicationStatusUserType", + "filePath": "src/main/java/com/example/tracker/entity/ApplicationStatusUserType.java", + "summary": "Hibernate UserType implementation for ApplicationStatus enum. Extends PostgreSQLEnumType, bridges Java enum \u2194 PostgreSQL VARCHAR via toDbValue()/fromDbValue() delegating to ApplicationStatus.getValue()/fromValue().", + "tags": [ + "hibernate", + "usertype", + "enum", + "postgresql", + "java" + ], + "complexity": "moderate" + }, + { + "id": "file:src/main/java/com/example/tracker/entity/CompanyCategory.java", + "type": "file", + "name": "CompanyCategory", + "filePath": "src/main/java/com/example/tracker/entity/CompanyCategory.java", + "summary": "Java enum for company industry categories (19 values): EDUCATION through OTHER. Each value maps to a lowercase/hyphenated PostgreSQL enum string (e.g., ENTERPRISE_SOFTWARE \u2192 'enterprise-software').", + "tags": [ + "enum", + "category", + "java" + ], + "complexity": "moderate" + }, + { + "id": "file:src/main/java/com/example/tracker/entity/CompanyCategoryUserType.java", + "type": "file", + "name": "CompanyCategoryUserType", + "filePath": "src/main/java/com/example/tracker/entity/CompanyCategoryUserType.java", + "summary": "Hibernate UserType for CompanyCategory enum. Extends PostgreSQLEnumType bridging Java enum \u2194 PostgreSQL VARCHAR.", + "tags": [ + "hibernate", + "usertype", + "enum", + "postgresql", + "java" + ], + "complexity": "moderate" + }, + { + "id": "file:src/main/java/com/example/tracker/entity/InterviewStage.java", + "type": "file", + "name": "InterviewStage", + "filePath": "src/main/java/com/example/tracker/entity/InterviewStage.java", + "summary": "@Entity mapped to java_spring.interview_stages. UUID primary key, @ManyToOne back-reference to Application (lazy), stageName VARCHAR(200), stageOrder int, isCompleted boolean, completedDate LocalDate, notes TEXT, performanceRating Integer (1-5), timestamps via @PrePersist/@PreUpdate.", + "tags": [ + "entity", + "jpa", + "interview-stage", + "java" + ], + "complexity": "moderate" + }, + { + "id": "file:src/main/java/com/example/tracker/entity/JobSource.java", + "type": "file", + "name": "JobSource", + "filePath": "src/main/java/com/example/tracker/entity/JobSource.java", + "summary": "Java enum for job discovery channels: RECRUITER, LINKEDIN, INDEED, FRIEND, COLLEAGUE, COMPANY_WEBSITE, OTHER. Hyphenated PostgreSQL values for COMPANY_WEBSITE ('company-website').", + "tags": [ + "enum", + "job-source", + "java" + ], + "complexity": "moderate" + }, + { + "id": "file:src/main/java/com/example/tracker/entity/JobSourceUserType.java", + "type": "file", + "name": "JobSourceUserType", + "filePath": "src/main/java/com/example/tracker/entity/JobSourceUserType.java", + "summary": "Hibernate UserType for JobSource enum. Extends PostgreSQLEnumType bridging Java enum \u2194 PostgreSQL VARCHAR.", + "tags": [ + "hibernate", + "usertype", + "enum", + "postgresql", + "java" + ], + "complexity": "moderate" + }, + { + "id": "file:src/main/java/com/example/tracker/entity/PostgreSQLEnumType.java", + "type": "file", + "name": "PostgreSQLEnumType", + "filePath": "src/main/java/com/example/tracker/entity/PostgreSQLEnumType.java", + "summary": "Abstract generic Hibernate UserType base class for mapping Java enums to PostgreSQL enum/VARCHAR columns. Implements nullSafeGet/nullSafeSet using Types.OTHER for PostgreSQL compatibility, plus equals, hashCode, deepCopy, assemble, disassemble. Subclasses provide toDbValue/fromDbValue.", + "tags": [ + "hibernate", + "usertype", + "abstract", + "postgresql", + "java" + ], + "complexity": "moderate" + }, + { + "id": "file:src/main/java/com/example/tracker/repository/ApplicationRepository.java", + "type": "file", + "name": "ApplicationRepository", + "filePath": "src/main/java/com/example/tracker/repository/ApplicationRepository.java", + "summary": "Spring Data JPA repository for Application entity. Extends JpaRepository and JpaSpecificationExecutor for dynamic filtering. Adds existsByJobPostingUrl(String) for duplicate-URL detection during CSV import.", + "tags": [ + "repository", + "spring-data", + "jpa", + "java" + ], + "complexity": "moderate" + }, + { + "id": "file:src/main/java/com/example/tracker/repository/ApplicationSnapshotRepository.java", + "type": "file", + "name": "ApplicationSnapshotRepository", + "filePath": "src/main/java/com/example/tracker/repository/ApplicationSnapshotRepository.java", + "summary": "Spring Data JPA repository for ApplicationSnapshot. Provides findByApplicationIdOrderBySequenceNumberDesc for history listing, a @Query JPQL method for max sequence number, and findFirstByApplicationIdAndSequenceNumberLessThan for computing diffs against the previous snapshot.", + "tags": [ + "repository", + "spring-data", + "jpa", + "history", + "java" + ], + "complexity": "moderate" + }, + { + "id": "file:src/main/java/com/example/tracker/repository/InterviewStageRepository.java", + "type": "file", + "name": "InterviewStageRepository", + "filePath": "src/main/java/com/example/tracker/repository/InterviewStageRepository.java", + "summary": "Spring Data JPA repository for InterviewStage entity. Provides findByApplicationIdOrderByStageOrderAsc for ordered stage retrieval.", + "tags": [ + "repository", + "spring-data", + "jpa", + "interview-stage", + "java" + ], + "complexity": "moderate" + }, + { + "id": "file:src/main/resources/application.properties", + "type": "config", + "name": "application.properties", + "filePath": "src/main/resources/application.properties", + "summary": "Spring Boot configuration. Server on port 8080, PostgreSQL datasource with java_spring schema, Hibernate DDL auto-validate (no auto-migration), Flyway configured for java_spring schema, open-in-view disabled, error messages included.", + "tags": [ + "config", + "spring-boot", + "database", + "flyway", + "properties" + ], + "complexity": "moderate" + }, + { + "id": "file:src/main/resources/db/migration/V1__initial.sql", + "type": "schema", + "name": "V1__initial.sql", + "filePath": "src/main/resources/db/migration/V1__initial.sql", + "summary": "Flyway migration V1: creates java_spring schema, defines PostgreSQL enums (application_status, company_category, job_source), and tables applications, interview_stages, application_snapshots with UUID PKs, constraints, and performance indexes.", + "tags": [ + "sql", + "migration", + "flyway", + "schema", + "postgresql" + ], + "complexity": "moderate" + }, + { + "id": "file:src/main/resources/db/migration/V2__increase_name_lengths.sql", + "type": "schema", + "name": "V2__increase_name_lengths.sql", + "filePath": "src/main/resources/db/migration/V2__increase_name_lengths.sql", + "summary": "Flyway migration V2: increases company_name and position_title column widths from VARCHAR(200) to VARCHAR(500).", + "tags": [ + "sql", + "migration", + "flyway", + "alter-table", + "postgresql" + ], + "complexity": "moderate" + }, + { + "id": "file:src/main/resources/db/migration/V3__url_columns_to_text.sql", + "type": "schema", + "name": "V3__url_columns_to_text.sql", + "filePath": "src/main/resources/db/migration/V3__url_columns_to_text.sql", + "summary": "Flyway migration V3: changes company_url, job_posting_url, and company_career_url from VARCHAR(500) to TEXT to support long LinkedIn URLs.", + "tags": [ + "sql", + "migration", + "flyway", + "alter-table", + "postgresql" + ], + "complexity": "moderate" + }, + { + "id": "file:src/test/java/com/example/tracker/controller/ApplicationControllerTest.java", + "type": "file", + "name": "ApplicationControllerTest", + "filePath": "src/test/java/com/example/tracker/controller/ApplicationControllerTest.java", + "summary": "@WebMvcTest slice for ApplicationController. Tests list returns 200 with items array, create returns 400 on missing required fields, and create returns 201 on valid body. Uses @MockitoBean for ApplicationService.", + "tags": [ + "test", + "controller", + "mockito", + "spring-mvc", + "java" + ], + "complexity": "moderate" + }, + { + "id": "file:src/test/java/com/example/tracker/service/ApplicationServiceTest.java", + "type": "file", + "name": "ApplicationServiceTest", + "filePath": "src/test/java/com/example/tracker/service/ApplicationServiceTest.java", + "summary": "Unit tests for ApplicationService using Mockito. Tests that create() saves the application and creates a snapshot, and that update() from APPLIED to INTERVIEWING automatically creates 6 default interview stages.", + "tags": [ + "test", + "service", + "mockito", + "unit-test", + "java" + ], + "complexity": "moderate" + }, + { + "id": "file:build.gradle.kts", + "type": "config", + "name": "build.gradle.kts", + "filePath": "build.gradle.kts", + "summary": "Gradle Kotlin build script. Configures Spring Boot 3.5.14 + Spring Dependency Management, Java 21 toolchain, PostgreSQL/JPA/Validation/Flyway/Test dependencies, Checkstyle 10.21.4, and OWASP dependency-check with NVD API key support. Parses .env file for bootRun.", + "tags": [ + "build", + "gradle", + "java", + "spring-boot", + "dependency-check" + ], + "complexity": "moderate" + }, + { + "id": "file:settings.gradle.kts", + "type": "config", + "name": "settings.gradle.kts", + "filePath": "settings.gradle.kts", + "summary": "Gradle settings file. Sets root project name to 'tracker'.", + "tags": [ + "build", + "gradle", + "config" + ], + "complexity": "moderate" + } + ], + "edges": [ + { + "source": "file:src/main/java/com/example/tracker/TrackerApplication.java", + "target": "file:src/main/java/com/example/tracker/config/JacksonConfig.java", + "type": "imports", + "label": "Spring context auto-configuration" + }, + { + "source": "file:src/main/java/com/example/tracker/TrackerApplication.java", + "target": "file:src/main/java/com/example/tracker/config/WebConfig.java", + "type": "imports", + "label": "Spring context auto-configuration" + }, + { + "source": "file:src/main/java/com/example/tracker/TrackerApplication.java", + "target": "file:src/main/java/com/example/tracker/controller/ApplicationController.java", + "type": "imports", + "label": "Spring context bootstrap" + }, + { + "source": "file:src/main/java/com/example/tracker/controller/ApplicationController.java", + "target": "file:src/main/java/com/example/tracker/service/ApplicationService.java", + "type": "uses", + "label": "delegates all business logic" + }, + { + "source": "file:src/main/java/com/example/tracker/controller/ApplicationController.java", + "target": "file:src/main/java/com/example/tracker/dto/ApplicationRequest.java", + "type": "uses", + "label": "@RequestBody" + }, + { + "source": "file:src/main/java/com/example/tracker/controller/ApplicationController.java", + "target": "file:src/main/java/com/example/tracker/dto/ApplicationResponse.java", + "type": "uses", + "label": "returns" + }, + { + "source": "file:src/main/java/com/example/tracker/controller/ApplicationController.java", + "target": "file:src/main/java/com/example/tracker/dto/InterviewStageRequest.java", + "type": "uses", + "label": "@RequestBody" + }, + { + "source": "file:src/main/java/com/example/tracker/controller/ApplicationController.java", + "target": "file:src/main/java/com/example/tracker/dto/InterviewStageResponse.java", + "type": "uses", + "label": "returns" + }, + { + "source": "file:src/main/java/com/example/tracker/controller/ApplicationController.java", + "target": "file:src/main/java/com/example/tracker/dto/PaginatedResponse.java", + "type": "uses", + "label": "returns" + }, + { + "source": "file:src/main/java/com/example/tracker/controller/ApplicationController.java", + "target": "file:src/main/java/com/example/tracker/dto/ImportResult.java", + "type": "uses", + "label": "returns" + }, + { + "source": "file:src/main/java/com/example/tracker/controller/ApplicationController.java", + "target": "file:src/main/java/com/example/tracker/dto/HistoryEntry.java", + "type": "uses", + "label": "returns" + }, + { + "source": "file:src/main/java/com/example/tracker/service/ApplicationService.java", + "target": "file:src/main/java/com/example/tracker/repository/ApplicationRepository.java", + "type": "uses", + "label": "injects" + }, + { + "source": "file:src/main/java/com/example/tracker/service/ApplicationService.java", + "target": "file:src/main/java/com/example/tracker/repository/InterviewStageRepository.java", + "type": "uses", + "label": "injects" + }, + { + "source": "file:src/main/java/com/example/tracker/service/ApplicationService.java", + "target": "file:src/main/java/com/example/tracker/repository/ApplicationSnapshotRepository.java", + "type": "uses", + "label": "injects" + }, + { + "source": "file:src/main/java/com/example/tracker/service/ApplicationService.java", + "target": "file:src/main/java/com/example/tracker/service/ApplicationSpecifications.java", + "type": "uses", + "label": "uses for filtering" + }, + { + "source": "file:src/main/java/com/example/tracker/service/ApplicationService.java", + "target": "file:src/main/java/com/example/tracker/entity/Application.java", + "type": "uses", + "label": "creates/reads/updates/deletes" + }, + { + "source": "file:src/main/java/com/example/tracker/service/ApplicationService.java", + "target": "file:src/main/java/com/example/tracker/entity/InterviewStage.java", + "type": "uses", + "label": "creates/updates" + }, + { + "source": "file:src/main/java/com/example/tracker/service/ApplicationService.java", + "target": "file:src/main/java/com/example/tracker/entity/ApplicationSnapshot.java", + "type": "uses", + "label": "saves snapshots" + }, + { + "source": "file:src/main/java/com/example/tracker/service/ApplicationService.java", + "target": "file:src/main/java/com/example/tracker/entity/ApplicationStatus.java", + "type": "uses", + "label": "parses status" + }, + { + "source": "file:src/main/java/com/example/tracker/service/ApplicationService.java", + "target": "file:src/main/java/com/example/tracker/entity/CompanyCategory.java", + "type": "uses", + "label": "parses category" + }, + { + "source": "file:src/main/java/com/example/tracker/service/ApplicationService.java", + "target": "file:src/main/java/com/example/tracker/entity/JobSource.java", + "type": "uses", + "label": "parses job source" + }, + { + "source": "file:src/main/java/com/example/tracker/service/ApplicationService.java", + "target": "file:src/main/java/com/example/tracker/dto/ApplicationRequest.java", + "type": "uses", + "label": "consumes" + }, + { + "source": "file:src/main/java/com/example/tracker/service/ApplicationService.java", + "target": "file:src/main/java/com/example/tracker/dto/ApplicationResponse.java", + "type": "uses", + "label": "produces" + }, + { + "source": "file:src/main/java/com/example/tracker/service/ApplicationService.java", + "target": "file:src/main/java/com/example/tracker/dto/HistoryDiff.java", + "type": "uses", + "label": "produces" + }, + { + "source": "file:src/main/java/com/example/tracker/service/ApplicationService.java", + "target": "file:src/main/java/com/example/tracker/dto/HistoryEntry.java", + "type": "uses", + "label": "produces" + }, + { + "source": "file:src/main/java/com/example/tracker/service/ApplicationService.java", + "target": "file:src/main/java/com/example/tracker/dto/ImportResult.java", + "type": "uses", + "label": "produces" + }, + { + "source": "file:src/main/java/com/example/tracker/service/ApplicationService.java", + "target": "file:src/main/java/com/example/tracker/dto/ImportError.java", + "type": "uses", + "label": "produces" + }, + { + "source": "file:src/main/java/com/example/tracker/service/ApplicationService.java", + "target": "file:src/main/java/com/example/tracker/dto/InterviewStageRequest.java", + "type": "uses", + "label": "consumes" + }, + { + "source": "file:src/main/java/com/example/tracker/service/ApplicationService.java", + "target": "file:src/main/java/com/example/tracker/dto/InterviewStageResponse.java", + "type": "uses", + "label": "produces" + }, + { + "source": "file:src/main/java/com/example/tracker/service/ApplicationService.java", + "target": "file:src/main/java/com/example/tracker/dto/PaginatedResponse.java", + "type": "uses", + "label": "produces" + }, + { + "source": "file:src/main/java/com/example/tracker/repository/ApplicationRepository.java", + "target": "file:src/main/java/com/example/tracker/entity/Application.java", + "type": "uses", + "label": "repository for" + }, + { + "source": "file:src/main/java/com/example/tracker/repository/ApplicationSnapshotRepository.java", + "target": "file:src/main/java/com/example/tracker/entity/ApplicationSnapshot.java", + "type": "uses", + "label": "repository for" + }, + { + "source": "file:src/main/java/com/example/tracker/repository/InterviewStageRepository.java", + "target": "file:src/main/java/com/example/tracker/entity/InterviewStage.java", + "type": "uses", + "label": "repository for" + }, + { + "source": "file:src/main/java/com/example/tracker/entity/Application.java", + "target": "file:src/main/java/com/example/tracker/entity/ApplicationStatus.java", + "type": "uses", + "label": "@Type enum" + }, + { + "source": "file:src/main/java/com/example/tracker/entity/Application.java", + "target": "file:src/main/java/com/example/tracker/entity/CompanyCategory.java", + "type": "uses", + "label": "@Type enum" + }, + { + "source": "file:src/main/java/com/example/tracker/entity/Application.java", + "target": "file:src/main/java/com/example/tracker/entity/JobSource.java", + "type": "uses", + "label": "@Type enum" + }, + { + "source": "file:src/main/java/com/example/tracker/entity/Application.java", + "target": "file:src/main/java/com/example/tracker/entity/InterviewStage.java", + "type": "uses", + "label": "@OneToMany" + }, + { + "source": "file:src/main/java/com/example/tracker/entity/Application.java", + "target": "file:src/main/java/com/example/tracker/entity/ApplicationStatusUserType.java", + "type": "uses", + "label": "@Type" + }, + { + "source": "file:src/main/java/com/example/tracker/entity/Application.java", + "target": "file:src/main/java/com/example/tracker/entity/CompanyCategoryUserType.java", + "type": "uses", + "label": "@Type" + }, + { + "source": "file:src/main/java/com/example/tracker/entity/Application.java", + "target": "file:src/main/java/com/example/tracker/entity/JobSourceUserType.java", + "type": "uses", + "label": "@Type" + }, + { + "source": "file:src/main/java/com/example/tracker/entity/ApplicationStatusUserType.java", + "target": "file:src/main/java/com/example/tracker/entity/PostgreSQLEnumType.java", + "type": "extends", + "label": "extends abstract base" + }, + { + "source": "file:src/main/java/com/example/tracker/entity/ApplicationStatusUserType.java", + "target": "file:src/main/java/com/example/tracker/entity/ApplicationStatus.java", + "type": "uses", + "label": "converts" + }, + { + "source": "file:src/main/java/com/example/tracker/entity/CompanyCategoryUserType.java", + "target": "file:src/main/java/com/example/tracker/entity/PostgreSQLEnumType.java", + "type": "extends", + "label": "extends abstract base" + }, + { + "source": "file:src/main/java/com/example/tracker/entity/CompanyCategoryUserType.java", + "target": "file:src/main/java/com/example/tracker/entity/CompanyCategory.java", + "type": "uses", + "label": "converts" + }, + { + "source": "file:src/main/java/com/example/tracker/entity/JobSourceUserType.java", + "target": "file:src/main/java/com/example/tracker/entity/PostgreSQLEnumType.java", + "type": "extends", + "label": "extends abstract base" + }, + { + "source": "file:src/main/java/com/example/tracker/entity/JobSourceUserType.java", + "target": "file:src/main/java/com/example/tracker/entity/JobSource.java", + "type": "uses", + "label": "converts" + }, + { + "source": "file:src/main/java/com/example/tracker/entity/InterviewStage.java", + "target": "file:src/main/java/com/example/tracker/entity/Application.java", + "type": "uses", + "label": "@ManyToOne" + }, + { + "source": "file:src/main/java/com/example/tracker/service/ApplicationSpecifications.java", + "target": "file:src/main/java/com/example/tracker/entity/Application.java", + "type": "uses", + "label": "Specification" + }, + { + "source": "file:src/main/java/com/example/tracker/service/ApplicationSpecifications.java", + "target": "file:src/main/java/com/example/tracker/entity/ApplicationStatus.java", + "type": "uses", + "label": "filters by" + }, + { + "source": "file:src/main/java/com/example/tracker/service/ApplicationSpecifications.java", + "target": "file:src/main/java/com/example/tracker/entity/CompanyCategory.java", + "type": "uses", + "label": "filters by" + }, + { + "source": "file:src/main/java/com/example/tracker/service/ApplicationSpecifications.java", + "target": "file:src/main/java/com/example/tracker/entity/JobSource.java", + "type": "uses", + "label": "filters by" + }, + { + "source": "file:src/test/java/com/example/tracker/controller/ApplicationControllerTest.java", + "target": "file:src/main/java/com/example/tracker/controller/ApplicationController.java", + "type": "tests", + "label": "@WebMvcTest" + }, + { + "source": "file:src/test/java/com/example/tracker/controller/ApplicationControllerTest.java", + "target": "file:src/main/java/com/example/tracker/service/ApplicationService.java", + "type": "uses", + "label": "@MockitoBean" + }, + { + "source": "file:src/test/java/com/example/tracker/service/ApplicationServiceTest.java", + "target": "file:src/main/java/com/example/tracker/service/ApplicationService.java", + "type": "tests", + "label": "unit tests" + }, + { + "source": "file:src/test/java/com/example/tracker/service/ApplicationServiceTest.java", + "target": "file:src/main/java/com/example/tracker/repository/ApplicationRepository.java", + "type": "uses", + "label": "@Mock" + }, + { + "source": "file:src/test/java/com/example/tracker/service/ApplicationServiceTest.java", + "target": "file:src/main/java/com/example/tracker/repository/InterviewStageRepository.java", + "type": "uses", + "label": "@Mock" + }, + { + "source": "file:src/test/java/com/example/tracker/service/ApplicationServiceTest.java", + "target": "file:src/main/java/com/example/tracker/repository/ApplicationSnapshotRepository.java", + "type": "uses", + "label": "@Mock" + }, + { + "source": "file:src/main/resources/db/migration/V1__initial.sql", + "target": "file:src/main/resources/application.properties", + "type": "uses", + "label": "Flyway auto-runs on startup" + }, + { + "source": "file:src/main/resources/db/migration/V2__increase_name_lengths.sql", + "target": "file:src/main/resources/db/migration/V1__initial.sql", + "type": "uses", + "label": "subsequent migration" + }, + { + "source": "file:src/main/resources/db/migration/V3__url_columns_to_text.sql", + "target": "file:src/main/resources/db/migration/V2__increase_name_lengths.sql", + "type": "uses", + "label": "subsequent migration" + }, + { + "source": "file:build.gradle.kts", + "target": "file:settings.gradle.kts", + "type": "uses", + "label": "project config" + }, + { + "source": "file:src/main/java/com/example/tracker/controller/ApplicationController.java", + "target": "file:src/main/java/com/example/tracker/controller/GlobalExceptionHandler.java", + "type": "uses", + "label": "@RestControllerAdvice intercepts exceptions from" + } + ], + "layers": [ + { + "id": "layer:bootstrap", + "name": "Bootstrap & Configuration", + "description": "Application entry point and cross-cutting Spring configuration beans: the @SpringBootApplication bootstrap class, Jackson ObjectMapper customization (ISO dates, camelCase), CORS configuration for the Angular frontend, and Gradle build/settings files.", + "nodeIds": [ + "file:src/main/java/com/example/tracker/TrackerApplication.java", + "file:src/main/java/com/example/tracker/config/JacksonConfig.java", + "file:src/main/java/com/example/tracker/config/WebConfig.java", + "file:src/main/resources/application.properties", + "file:build.gradle.kts", + "file:settings.gradle.kts" + ] + }, + { + "id": "layer:api", + "name": "REST API Layer", + "description": "HTTP interface: the ApplicationController @RestController exposing all /api/applications endpoints (CRUD, archive/restore, interview stages, history, CSV import/export) and the GlobalExceptionHandler @RestControllerAdvice mapping exceptions to appropriate HTTP responses.", + "nodeIds": [ + "file:src/main/java/com/example/tracker/controller/ApplicationController.java", + "file:src/main/java/com/example/tracker/controller/GlobalExceptionHandler.java" + ] + }, + { + "id": "layer:dto", + "name": "Data Transfer Objects", + "description": "Java records used as request/response contracts between the HTTP layer and the service layer: ApplicationRequest/Response, InterviewStageRequest/Response, PaginatedResponse, HistoryEntry, HistoryDiff, ImportResult, ImportError.", + "nodeIds": [ + "file:src/main/java/com/example/tracker/dto/ApplicationRequest.java", + "file:src/main/java/com/example/tracker/dto/ApplicationResponse.java", + "file:src/main/java/com/example/tracker/dto/InterviewStageRequest.java", + "file:src/main/java/com/example/tracker/dto/InterviewStageResponse.java", + "file:src/main/java/com/example/tracker/dto/PaginatedResponse.java", + "file:src/main/java/com/example/tracker/dto/HistoryEntry.java", + "file:src/main/java/com/example/tracker/dto/HistoryDiff.java", + "file:src/main/java/com/example/tracker/dto/ImportResult.java", + "file:src/main/java/com/example/tracker/dto/ImportError.java" + ] + }, + { + "id": "layer:service", + "name": "Service / Business Logic", + "description": "Core business logic: ApplicationService implements all CRUD, archive, interview-stage management, history snapshotting, CSV import/export, and dynamic filtering via JPA Specifications. ApplicationSpecifications provides composable Specification factories.", + "nodeIds": [ + "file:src/main/java/com/example/tracker/service/ApplicationService.java", + "file:src/main/java/com/example/tracker/service/ApplicationSpecifications.java" + ] + }, + { + "id": "layer:domain", + "name": "Domain Entities & Enums", + "description": "JPA entity classes (Application, InterviewStage, ApplicationSnapshot) and domain enums (ApplicationStatus, CompanyCategory, JobSource) with their Hibernate UserType adapters (PostgreSQLEnumType base class plus three concrete subclasses) for mapping Java enums to PostgreSQL native enum columns.", + "nodeIds": [ + "file:src/main/java/com/example/tracker/entity/Application.java", + "file:src/main/java/com/example/tracker/entity/InterviewStage.java", + "file:src/main/java/com/example/tracker/entity/ApplicationSnapshot.java", + "file:src/main/java/com/example/tracker/entity/ApplicationStatus.java", + "file:src/main/java/com/example/tracker/entity/CompanyCategory.java", + "file:src/main/java/com/example/tracker/entity/JobSource.java", + "file:src/main/java/com/example/tracker/entity/PostgreSQLEnumType.java", + "file:src/main/java/com/example/tracker/entity/ApplicationStatusUserType.java", + "file:src/main/java/com/example/tracker/entity/CompanyCategoryUserType.java", + "file:src/main/java/com/example/tracker/entity/JobSourceUserType.java" + ] + }, + { + "id": "layer:persistence", + "name": "Repository / Persistence", + "description": "Spring Data JPA repositories: ApplicationRepository (with JpaSpecificationExecutor and duplicate-URL check), InterviewStageRepository (ordered by stageOrder), ApplicationSnapshotRepository (sequence-based history with max-sequence query and prev-snapshot lookup).", + "nodeIds": [ + "file:src/main/java/com/example/tracker/repository/ApplicationRepository.java", + "file:src/main/java/com/example/tracker/repository/InterviewStageRepository.java", + "file:src/main/java/com/example/tracker/repository/ApplicationSnapshotRepository.java" + ] + }, + { + "id": "layer:database", + "name": "Database Migrations", + "description": "Flyway SQL migrations defining the java_spring PostgreSQL schema: V1 creates the schema, enums, and all three tables with indexes; V2 widens name columns to 500 chars; V3 changes URL columns to TEXT for LinkedIn URL support.", + "nodeIds": [ + "file:src/main/resources/db/migration/V1__initial.sql", + "file:src/main/resources/db/migration/V2__increase_name_lengths.sql", + "file:src/main/resources/db/migration/V3__url_columns_to_text.sql" + ] + }, + { + "id": "layer:tests", + "name": "Tests", + "description": "JUnit 5 test classes: ApplicationControllerTest uses @WebMvcTest slice + Mockito to verify HTTP contract (200 list, 400 validation, 201 create), and ApplicationServiceTest uses pure Mockito to verify snapshot creation and automatic default-stage creation on status transition.", + "nodeIds": [ + "file:src/test/java/com/example/tracker/controller/ApplicationControllerTest.java", + "file:src/test/java/com/example/tracker/service/ApplicationServiceTest.java" + ] + } + ], + "tour": [ + { + "id": "step:entrypoint", + "title": "Application Bootstrap", + "description": "Start here: TrackerApplication.java is the Spring Boot entry point. The single @SpringBootApplication annotation triggers component scanning for all beans in the com.example.tracker package. The Gradle build (build.gradle.kts) wires Java 21, Spring Boot 3.5.14, Hibernate 6, and Flyway.", + "nodeIds": [ + "file:src/main/java/com/example/tracker/TrackerApplication.java", + "file:build.gradle.kts" + ], + "order": 1 + }, + { + "id": "step:config", + "title": "Cross-Cutting Configuration", + "description": "Two @Configuration beans shape global behavior: WebConfig registers CORS for the Angular frontend (localhost:3070), and JacksonConfig customizes the ObjectMapper to produce ISO date strings instead of arrays and use camelCase property names \u2014 critical for the TypeScript frontend.", + "nodeIds": [ + "file:src/main/java/com/example/tracker/config/WebConfig.java", + "file:src/main/java/com/example/tracker/config/JacksonConfig.java", + "file:src/main/resources/application.properties" + ], + "order": 2 + }, + { + "id": "step:database", + "title": "Database Schema (Flyway Migrations)", + "description": "Flyway auto-runs migrations on startup. V1 creates the java_spring schema, three PostgreSQL native enum types (application_status, company_category, job_source), the applications, interview_stages, and application_snapshots tables with UUID PKs and covering indexes. V2 and V3 widen column sizes to handle long names and LinkedIn URLs.", + "nodeIds": [ + "file:src/main/resources/db/migration/V1__initial.sql", + "file:src/main/resources/db/migration/V2__increase_name_lengths.sql", + "file:src/main/resources/db/migration/V3__url_columns_to_text.sql" + ], + "order": 3 + }, + { + "id": "step:domain", + "title": "Domain Entities & Enum Type System", + "description": "Application is the central JPA entity with a @OneToMany to InterviewStage and three PostgreSQL native enum fields. Because Hibernate's @Enumerated(STRING) doesn't work with hyphenated/spaced PostgreSQL enum values (e.g. 'enterprise-software'), the codebase uses a custom Hibernate UserType system: PostgreSQLEnumType is the abstract base, and ApplicationStatusUserType, CompanyCategoryUserType, JobSourceUserType each extend it to convert between Java enums and their PostgreSQL string values. ApplicationSnapshot stores a JSONB blob of the full ApplicationResponse for history replay.", + "nodeIds": [ + "file:src/main/java/com/example/tracker/entity/Application.java", + "file:src/main/java/com/example/tracker/entity/InterviewStage.java", + "file:src/main/java/com/example/tracker/entity/ApplicationSnapshot.java", + "file:src/main/java/com/example/tracker/entity/PostgreSQLEnumType.java", + "file:src/main/java/com/example/tracker/entity/ApplicationStatus.java", + "file:src/main/java/com/example/tracker/entity/ApplicationStatusUserType.java" + ], + "order": 4 + }, + { + "id": "step:repositories", + "title": "Spring Data JPA Repositories", + "description": "Three thin repository interfaces provide all data access. ApplicationRepository extends JpaSpecificationExecutor enabling dynamic multi-criteria filtering. ApplicationSnapshotRepository has a @Query for max sequence number and a derived method to fetch the previous snapshot \u2014 both used by the diff engine. InterviewStageRepository provides ordered stage retrieval.", + "nodeIds": [ + "file:src/main/java/com/example/tracker/repository/ApplicationRepository.java", + "file:src/main/java/com/example/tracker/repository/ApplicationSnapshotRepository.java", + "file:src/main/java/com/example/tracker/repository/InterviewStageRepository.java" + ], + "order": 5 + }, + { + "id": "step:service", + "title": "Application Service (Core Business Logic)", + "description": "ApplicationService is the heart of the codebase. Notable patterns: (1) Every mutating operation calls captureSnapshot() which serializes the full ApplicationResponse to JSONB; (2) Transitioning to INTERVIEWING status with no existing stages auto-creates 6 default stages; (3) CSV import uses Propagation.NOT_SUPPORTED + TransactionTemplate per row to prevent a single bad row from rolling back the whole batch; (4) ApplicationSpecifications.java provides composable Specification factories composed with Specification.allOf() for filtering.", + "nodeIds": [ + "file:src/main/java/com/example/tracker/service/ApplicationService.java", + "file:src/main/java/com/example/tracker/service/ApplicationSpecifications.java" + ], + "order": 6 + }, + { + "id": "step:dto", + "title": "Data Transfer Objects (Java Records)", + "description": "All API contracts use Java records \u2014 immutable, concise value types. ApplicationRequest has Bean Validation annotations (@NotBlank, @Size, @Min, @Max). ApplicationResponse embeds a list of InterviewStageResponse. PaginatedResponse is a generic wrapper. HistoryEntry + HistoryDiff carry computed diffs. ImportResult/ImportError summarize CSV batch operations.", + "nodeIds": [ + "file:src/main/java/com/example/tracker/dto/ApplicationRequest.java", + "file:src/main/java/com/example/tracker/dto/ApplicationResponse.java", + "file:src/main/java/com/example/tracker/dto/PaginatedResponse.java", + "file:src/main/java/com/example/tracker/dto/HistoryEntry.java", + "file:src/main/java/com/example/tracker/dto/HistoryDiff.java", + "file:src/main/java/com/example/tracker/dto/ImportResult.java" + ], + "order": 7 + }, + { + "id": "step:controller", + "title": "REST Controller & Exception Handler", + "description": "ApplicationController delegates everything to ApplicationService \u2014 it contains no business logic. GlobalExceptionHandler @RestControllerAdvice translates domain exceptions to HTTP: EntityNotFoundException\u2192404, MethodArgumentNotValidException\u2192400 field-error map, HttpMessageNotReadableException\u2192400 validation_error, RuntimeException\u2192500. The order of @ExceptionHandler methods matters \u2014 HttpMessageNotReadableException must appear before RuntimeException.", + "nodeIds": [ + "file:src/main/java/com/example/tracker/controller/ApplicationController.java", + "file:src/main/java/com/example/tracker/controller/GlobalExceptionHandler.java" + ], + "order": 8 + }, + { + "id": "step:tests", + "title": "Tests", + "description": "ApplicationControllerTest uses @WebMvcTest (loads only the web layer) with @MockitoBean for the service \u2014 fast, no DB. ApplicationServiceTest uses pure Mockito with @ExtendWith(MockitoExtension.class), constructing ApplicationService directly with a real ObjectMapper to test JSON serialization. Key cases: snapshot creation on create(), and automatic default-stage insertion when transitioning to INTERVIEWING.", + "nodeIds": [ + "file:src/test/java/com/example/tracker/controller/ApplicationControllerTest.java", + "file:src/test/java/com/example/tracker/service/ApplicationServiceTest.java" + ], + "order": 9 + } + ] +} \ No newline at end of file diff --git a/spring-api/.understand-anything/meta.json b/spring-api/.understand-anything/meta.json new file mode 100644 index 00000000..c9a8f214 --- /dev/null +++ b/spring-api/.understand-anything/meta.json @@ -0,0 +1,27 @@ +{ + "projectName": "spring-api", + "description": "Java Spring Boot 3 REST API for job application tracking. Uses Spring Data JPA with Hibernate 6, Flyway for DB migrations (java_spring PostgreSQL schema), and Bean Validation. Runs on port 8080, pairs with angular-spring-ui on port 3070.", + "gitHash": "e5054096efec8165e7d4cfe11fc103f77ad2d98f", + "analyzedAt": "2026-05-04T00:00:00Z", + "pluginVersion": "2.5.1", + "languages": [ + "Java", + "SQL", + "Kotlin" + ], + "frameworks": [ + "Spring Boot 3", + "Spring Data JPA", + "Hibernate 6", + "Flyway", + "JUnit 5", + "Mockito" + ], + "stats": { + "totalFiles": 37, + "totalNodes": 37, + "totalEdges": 62, + "totalLayers": 8, + "tourSteps": 9 + } +} diff --git a/svelte-ui/.understand-anything/.understandignore b/svelte-ui/.understand-anything/.understandignore new file mode 100644 index 00000000..a15816b1 --- /dev/null +++ b/svelte-ui/.understand-anything/.understandignore @@ -0,0 +1,6 @@ +# .understandignore — patterns for files/dirs to exclude from analysis +# Built-in defaults (always excluded): node_modules/, .git/, dist/, build/, *.lock + +# Exclude SvelteKit generated dir and static assets +.svelte-kit/ +static/ diff --git a/svelte-ui/.understand-anything/knowledge-graph.json b/svelte-ui/.understand-anything/knowledge-graph.json new file mode 100644 index 00000000..32f6113a --- /dev/null +++ b/svelte-ui/.understand-anything/knowledge-graph.json @@ -0,0 +1,489 @@ +{ + "nodes": [ + { + "id": "file:src/app.html", + "type": "file", + "name": "app.html", + "filePath": "src/app.html", + "summary": "Root HTML shell for the SvelteKit SPA. Sets lang, viewport, preload strategy, and mounts %sveltekit.body%.", + "tags": ["html", "entrypoint", "sveltekit", "shell"] + }, + { + "id": "file:src/app.css", + "type": "file", + "name": "app.css", + "filePath": "src/app.css", + "summary": "Global stylesheet importing Tailwind CSS 4 and @tailwindcss/forms plugin. Defines custom primary color palette and reusable component classes: .btn-primary, .btn-secondary, .btn-danger, .input, .label, .card.", + "tags": ["css", "tailwind", "design-system", "styles"] + }, + { + "id": "file:src/routes/+layout.ts", + "type": "file", + "name": "+layout.ts", + "filePath": "src/routes/+layout.ts", + "summary": "Disables SSR with `export const ssr = false`, making the app a pure client-side SPA to avoid hydration timing issues.", + "tags": ["sveltekit", "config", "ssr", "routing"] + }, + { + "id": "file:src/routes/+layout.svelte", + "type": "file", + "name": "+layout.svelte", + "filePath": "src/routes/+layout.svelte", + "summary": "Root layout wrapping all routes. Renders navigation bar with Svelte logo and app title, dark-mode toggle (persisted to localStorage using system preference default), and a max-width content container that renders child routes via {@render children()}.", + "tags": ["sveltekit", "layout", "navigation", "dark-mode", "svelte5"] + }, + { + "id": "file:src/routes/+page.svelte", + "type": "file", + "name": "+page.svelte (home)", + "filePath": "src/routes/+page.svelte", + "summary": "Home/list page. Shows the FilterBar, paginated ApplicationCard grid, empty/loading/error states, and a ConfirmDialog for delete. Reacts to filter changes via $effect to reload from applicationStore. Handles archive/restore and delete via store methods.", + "tags": ["sveltekit", "page", "list-view", "svelte5"] + }, + { + "id": "file:src/routes/applications/[id]/+page.svelte", + "type": "file", + "name": "+page.svelte ([id])", + "filePath": "src/routes/applications/[id]/+page.svelte", + "summary": "Detail/edit page for an existing application. Derives the id from $page.params and delegates all rendering to the ApplicationEdit component.", + "tags": ["sveltekit", "page", "dynamic-route", "detail-view"] + }, + { + "id": "file:src/routes/applications/new/+page.svelte", + "type": "file", + "name": "+page.svelte (new)", + "filePath": "src/routes/applications/new/+page.svelte", + "summary": "Create page. Renders ApplicationEdit without an id prop, switching it into create mode.", + "tags": ["sveltekit", "page", "create-view"] + }, + { + "id": "file:src/lib/types/index.ts", + "type": "file", + "name": "types/index.ts", + "filePath": "src/lib/types/index.ts", + "summary": "Central type module. Exports domain interfaces (Application, InterviewStage, FilterState, HistoryEntry, FieldChange, PaginatedResponse), enum union types (ApplicationStatus, CompanyCategory, JobSource), and display-helper constants (STATUS_LABELS, CATEGORY_LABELS, SOURCE_LABELS, STATUS_COLORS, ALL_STATUSES, ALL_CATEGORIES, ALL_SOURCES).", + "tags": ["types", "domain-model", "constants", "typescript"] + }, + { + "id": "file:src/lib/types/index.test.ts", + "type": "file", + "name": "types/index.test.ts", + "filePath": "src/lib/types/index.test.ts", + "summary": "Unit tests verifying that STATUS_LABELS and STATUS_COLORS have entries for every ApplicationStatus in ALL_STATUSES, and that 'unsubmitted' is the first status.", + "tags": ["test", "vitest", "types"] + }, + { + "id": "file:src/lib/stores/api.ts", + "type": "file", + "name": "stores/api.ts", + "filePath": "src/lib/stores/api.ts", + "summary": "HTTP API client singleton. Wraps all fetch calls to /api (proxied to hono-api). Provides typed methods for applications CRUD (list, get, create, update, delete, archive, restore), interview stages CRUD, and history (getHistory, restoreToVersion). Handles 204 No Content and surfaces error messages.", + "tags": ["api-client", "http", "fetch", "singleton", "typescript"] + }, + { + "id": "file:src/lib/stores/applications.svelte.ts", + "type": "file", + "name": "applications.svelte.ts", + "filePath": "src/lib/stores/applications.svelte.ts", + "summary": "Svelte 5 runes-based application store using a factory function pattern. Manages reactive state (applications array, total, page, limit, loading, error, filters) with $state and $derived. Exposes load, create, update, remove, archive, restore, setFilters, setPage, resetFilters. Exports singleton `applicationStore`.", + "tags": ["store", "svelte5", "runes", "state-management", "reactive"] + }, + { + "id": "file:src/lib/components/ApplicationCard.svelte", + "type": "file", + "name": "ApplicationCard.svelte", + "filePath": "src/lib/components/ApplicationCard.svelte", + "summary": "Card component showing a single application in the list. Displays company name with StatusBadge, position title, applied date, category tag, RatingDisplay for skills match, interview stage progress, and offer due-date urgency. Has an overflow menu (Archive/Delete) with click-outside isolation via stopPropagation.", + "tags": ["component", "svelte5", "list-item", "card"] + }, + { + "id": "file:src/lib/components/ApplicationDetail.svelte", + "type": "file", + "name": "ApplicationDetail.svelte", + "filePath": "src/lib/components/ApplicationDetail.svelte", + "summary": "Slide-over detail panel (legacy). Shows full application details in read mode or embeds ApplicationForm for editing. Includes status quick-change select, salary formatting, links section, notes, and InterviewStageList. Used in an older panel-based layout (now superseded by ApplicationEdit route pattern).", + "tags": ["component", "svelte5", "detail-view", "panel"] + }, + { + "id": "file:src/lib/components/ApplicationEdit.svelte", + "type": "file", + "name": "ApplicationEdit.svelte", + "filePath": "src/lib/components/ApplicationEdit.svelte", + "summary": "Primary create/edit form for applications. Handles both create mode (no id prop) and edit mode (id prop). Features: full form state with $state runes, snapshot-based dirty tracking (isDirty), navigation guard via beforeNavigate + window.beforeunload, status-to-date enforcement, inline interview stage management (add/edit/delete/toggle-complete), history panel toggle, and confirm dialogs for discard/delete/stage-delete.", + "tags": ["component", "svelte5", "form", "create", "edit", "validation", "navigation-guard"] + }, + { + "id": "file:src/lib/components/ApplicationForm.svelte", + "type": "file", + "name": "ApplicationForm.svelte", + "filePath": "src/lib/components/ApplicationForm.svelte", + "summary": "Reusable form for application create/update fields. Accepts optional `application` prop for pre-population. Handles both CreateApplicationInput and UpdateApplicationInput shapes. Used inside ApplicationDetail for inline editing.", + "tags": ["component", "svelte5", "form", "reusable"] + }, + { + "id": "file:src/lib/components/ConfirmDialog.svelte", + "type": "file", + "name": "ConfirmDialog.svelte", + "filePath": "src/lib/components/ConfirmDialog.svelte", + "summary": "Modal confirmation dialog. Props: title, message, confirmLabel, cancelLabel, confirmVariant (primary|danger), onconfirm, oncancel. Closes on Escape key or backdrop click. Uses Tailwind fixed overlay.", + "tags": ["component", "svelte5", "modal", "dialog", "accessibility"] + }, + { + "id": "file:src/lib/components/EmptyState.svelte", + "type": "file", + "name": "EmptyState.svelte", + "filePath": "src/lib/components/EmptyState.svelte", + "summary": "Centered empty-state placeholder with clipboard icon, configurable title, description, and optional action button.", + "tags": ["component", "svelte5", "empty-state", "ui"] + }, + { + "id": "file:src/lib/components/FieldDiff.svelte", + "type": "file", + "name": "FieldDiff.svelte", + "filePath": "src/lib/components/FieldDiff.svelte", + "summary": "Renders a list of FieldChange objects as before/after diffs with red strikethrough for old values and green for new values. Used inside HistoryPanel.", + "tags": ["component", "svelte5", "history", "diff"] + }, + { + "id": "file:src/lib/components/FilterBar.svelte", + "type": "file", + "name": "FilterBar.svelte", + "filePath": "src/lib/components/FilterBar.svelte", + "summary": "Filter toolbar for the list view. Provides select dropdowns for Status, Category, Source, Min Skills Match, and Sort By with direction toggle. Includes 'Include archived' checkbox and result count display. Emits partial FilterState updates via onchange callback.", + "tags": ["component", "svelte5", "filter", "toolbar"] + }, + { + "id": "file:src/lib/components/HistoryPanel.svelte", + "type": "file", + "name": "HistoryPanel.svelte", + "filePath": "src/lib/components/HistoryPanel.svelte", + "summary": "Slide-over panel (fixed right, w-96) showing paginated history entries for an application. Each entry is expandable to show FieldDiff changes. Non-current entries have a 'Restore to this point' button that calls api.restoreToVersion and triggers onrestored callback.", + "tags": ["component", "svelte5", "history", "panel", "slide-over"] + }, + { + "id": "file:src/lib/components/InterviewStageForm.svelte", + "type": "file", + "name": "InterviewStageForm.svelte", + "filePath": "src/lib/components/InterviewStageForm.svelte", + "summary": "Inline form for adding or editing an interview stage. Fields: name (required), order, isCompleted checkbox, completedDate (conditional), performanceRating (via RatingInput), notes. Submits CreateInterviewStageInput or UpdateInterviewStageInput based on whether a stage prop is provided.", + "tags": ["component", "svelte5", "form", "interview-stage"] + }, + { + "id": "file:src/lib/components/InterviewStageItem.svelte", + "type": "file", + "name": "InterviewStageItem.svelte", + "filePath": "src/lib/components/InterviewStageItem.svelte", + "summary": "Single interview stage row. Shows a toggle-complete checkbox, stage name (struck through when completed), completed date, performance rating (RatingDisplay), notes icon, and hover-revealed edit/delete buttons.", + "tags": ["component", "svelte5", "interview-stage", "list-item"] + }, + { + "id": "file:src/lib/components/InterviewStageList.svelte", + "type": "file", + "name": "InterviewStageList.svelte", + "filePath": "src/lib/components/InterviewStageList.svelte", + "summary": "Container for interview stages within ApplicationDetail. Renders sorted InterviewStageItem list with completion count, an Add Stage button, and inline InterviewStageForm for add/edit. Delegates CRUD to onAdd/onUpdate/onRemove callback props.", + "tags": ["component", "svelte5", "interview-stage", "container"] + }, + { + "id": "file:src/lib/components/Pagination.svelte", + "type": "file", + "name": "Pagination.svelte", + "filePath": "src/lib/components/Pagination.svelte", + "summary": "Smart pagination control. Renders Previous/Next buttons and up to 7 page numbers with ellipsis compression for large page counts. Highlights current page and sets aria-current.", + "tags": ["component", "svelte5", "pagination", "navigation", "accessibility"] + }, + { + "id": "file:src/lib/components/RatingDisplay.svelte", + "type": "file", + "name": "RatingDisplay.svelte", + "filePath": "src/lib/components/RatingDisplay.svelte", + "summary": "Read-only star rating display. Accepts value (1-5), max (default 5), and optional showNumeric flag. Renders filled/empty SVG stars.", + "tags": ["component", "svelte5", "rating", "display"] + }, + { + "id": "file:src/lib/components/RatingInput.svelte", + "type": "file", + "name": "RatingInput.svelte", + "filePath": "src/lib/components/RatingInput.svelte", + "summary": "Interactive 5-star rating input. Implements ARIA radiogroup pattern with roving tabindex. Supports hover preview, click to set, click again to clear (allowClear), Space/Enter keyboard activation.", + "tags": ["component", "svelte5", "rating", "input", "accessibility", "aria"] + }, + { + "id": "file:src/lib/components/RatingInput.test.ts", + "type": "file", + "name": "RatingInput.test.ts", + "filePath": "src/lib/components/RatingInput.test.ts", + "summary": "Comprehensive unit tests for RatingInput covering ARIA roles, roving tabindex, keyboard interaction (Space, Enter), mouse click/clear, and visual focus-ring classes. Uses @testing-library/svelte and userEvent.", + "tags": ["test", "vitest", "component-test", "accessibility", "rating"] + }, + { + "id": "file:src/lib/components/StatusBadge.svelte", + "type": "file", + "name": "StatusBadge.svelte", + "filePath": "src/lib/components/StatusBadge.svelte", + "summary": "Inline pill badge for ApplicationStatus. Maps status to Tailwind color classes via STATUS_COLORS and label text via STATUS_LABELS. Supports small/medium size variants.", + "tags": ["component", "svelte5", "badge", "status"] + }, + { + "id": "file:src/lib/components/UrlFieldInput.svelte", + "type": "file", + "name": "UrlFieldInput.svelte", + "filePath": "src/lib/components/UrlFieldInput.svelte", + "summary": "URL input field with an inline 'open in new tab' icon button that appears when the value is a valid http(s) URL. Uses a local $state copy synced from the parent prop via $effect to ensure Playwright fill() works correctly.", + "tags": ["component", "svelte5", "input", "url", "form"] + }, + { + "id": "file:svelte.config.js", + "type": "config", + "name": "svelte.config.js", + "filePath": "svelte.config.js", + "summary": "SvelteKit configuration. Uses adapter-auto and vitePreprocess. Defines $lib and $components path aliases.", + "tags": ["config", "sveltekit", "build"] + }, + { + "id": "file:vite.config.ts", + "type": "config", + "name": "vite.config.ts", + "filePath": "vite.config.ts", + "summary": "Vite config with SvelteKit plugin. Dev server on port 3030 (env: UI_PORT). Proxies /api to localhost:5030 (env: API_PORT) rewriting the prefix. Configures Vitest with happy-dom environment and browser conditions.", + "tags": ["config", "vite", "proxy", "test", "dev-server"] + }, + { + "id": "file:package.json", + "type": "config", + "name": "package.json", + "filePath": "package.json", + "summary": "Package manifest for job-tracker-svelte-ui. Key deps: svelte 5, @sveltejs/kit 2, tailwindcss 4, vitest 4, @testing-library/svelte 5. Scripts: dev, build, lint, check, test.", + "tags": ["config", "npm", "dependencies"] + } + ], + "edges": [ + { "source": "file:src/routes/+layout.svelte", "target": "file:src/app.css", "type": "imports", "label": "imports app.css" }, + { "source": "file:src/routes/+layout.svelte", "target": "file:src/routes/+page.svelte", "type": "renders", "label": "renders children (routes)" }, + { "source": "file:src/routes/+layout.svelte", "target": "file:src/routes/applications/[id]/+page.svelte", "type": "renders", "label": "renders children (routes)" }, + { "source": "file:src/routes/+layout.svelte", "target": "file:src/routes/applications/new/+page.svelte", "type": "renders", "label": "renders children (routes)" }, + { "source": "file:src/routes/+layout.ts", "target": "file:src/routes/+layout.svelte", "type": "configures", "label": "ssr=false" }, + { "source": "file:src/routes/+page.svelte", "target": "file:src/lib/stores/applications.svelte.ts", "type": "imports", "label": "uses applicationStore" }, + { "source": "file:src/routes/+page.svelte", "target": "file:src/lib/components/ApplicationCard.svelte", "type": "imports", "label": "renders list" }, + { "source": "file:src/routes/+page.svelte", "target": "file:src/lib/components/FilterBar.svelte", "type": "imports", "label": "renders filter bar" }, + { "source": "file:src/routes/+page.svelte", "target": "file:src/lib/components/Pagination.svelte", "type": "imports", "label": "renders pagination" }, + { "source": "file:src/routes/+page.svelte", "target": "file:src/lib/components/EmptyState.svelte", "type": "imports", "label": "renders empty state" }, + { "source": "file:src/routes/+page.svelte", "target": "file:src/lib/components/ConfirmDialog.svelte", "type": "imports", "label": "delete confirmation" }, + { "source": "file:src/routes/applications/[id]/+page.svelte", "target": "file:src/lib/components/ApplicationEdit.svelte", "type": "imports", "label": "delegates to ApplicationEdit" }, + { "source": "file:src/routes/applications/new/+page.svelte", "target": "file:src/lib/components/ApplicationEdit.svelte", "type": "imports", "label": "delegates to ApplicationEdit (no id)" }, + { "source": "file:src/lib/stores/applications.svelte.ts", "target": "file:src/lib/stores/api.ts", "type": "imports", "label": "calls api for CRUD" }, + { "source": "file:src/lib/stores/applications.svelte.ts", "target": "file:src/lib/types/index.ts", "type": "imports", "label": "uses Application, FilterState types" }, + { "source": "file:src/lib/stores/api.ts", "target": "file:src/lib/types/index.ts", "type": "imports", "label": "uses all domain types" }, + { "source": "file:src/lib/components/ApplicationEdit.svelte", "target": "file:src/lib/stores/api.ts", "type": "imports", "label": "direct API calls for get/update/delete/stages/history" }, + { "source": "file:src/lib/components/ApplicationEdit.svelte", "target": "file:src/lib/stores/applications.svelte.ts", "type": "imports", "label": "calls applicationStore.create" }, + { "source": "file:src/lib/components/ApplicationEdit.svelte", "target": "file:src/lib/types/index.ts", "type": "imports", "label": "uses all domain types and constants" }, + { "source": "file:src/lib/components/ApplicationEdit.svelte", "target": "file:src/lib/components/RatingInput.svelte", "type": "imports", "label": "skills match rating" }, + { "source": "file:src/lib/components/ApplicationEdit.svelte", "target": "file:src/lib/components/UrlFieldInput.svelte", "type": "imports", "label": "URL fields" }, + { "source": "file:src/lib/components/ApplicationEdit.svelte", "target": "file:src/lib/components/InterviewStageForm.svelte", "type": "imports", "label": "add/edit stage form" }, + { "source": "file:src/lib/components/ApplicationEdit.svelte", "target": "file:src/lib/components/InterviewStageItem.svelte", "type": "imports", "label": "stage list items" }, + { "source": "file:src/lib/components/ApplicationEdit.svelte", "target": "file:src/lib/components/ConfirmDialog.svelte", "type": "imports", "label": "discard/delete/stage confirm dialogs" }, + { "source": "file:src/lib/components/ApplicationEdit.svelte", "target": "file:src/lib/components/HistoryPanel.svelte", "type": "imports", "label": "history slide-over" }, + { "source": "file:src/lib/components/ApplicationCard.svelte", "target": "file:src/lib/types/index.ts", "type": "imports", "label": "uses Application, CATEGORY_LABELS" }, + { "source": "file:src/lib/components/ApplicationCard.svelte", "target": "file:src/lib/components/StatusBadge.svelte", "type": "imports", "label": "shows status badge" }, + { "source": "file:src/lib/components/ApplicationCard.svelte", "target": "file:src/lib/components/RatingDisplay.svelte", "type": "imports", "label": "shows skills match stars" }, + { "source": "file:src/lib/components/ApplicationDetail.svelte", "target": "file:src/lib/types/index.ts", "type": "imports", "label": "uses Application and input types" }, + { "source": "file:src/lib/components/ApplicationDetail.svelte", "target": "file:src/lib/components/RatingDisplay.svelte", "type": "imports", "label": "skills match display" }, + { "source": "file:src/lib/components/ApplicationDetail.svelte", "target": "file:src/lib/components/InterviewStageList.svelte", "type": "imports", "label": "stage management" }, + { "source": "file:src/lib/components/ApplicationDetail.svelte", "target": "file:src/lib/components/ApplicationForm.svelte", "type": "imports", "label": "inline edit form" }, + { "source": "file:src/lib/components/ApplicationForm.svelte", "target": "file:src/lib/types/index.ts", "type": "imports", "label": "uses Application, input types, constants" }, + { "source": "file:src/lib/components/ApplicationForm.svelte", "target": "file:src/lib/components/RatingInput.svelte", "type": "imports", "label": "skills match rating" }, + { "source": "file:src/lib/components/FilterBar.svelte", "target": "file:src/lib/types/index.ts", "type": "imports", "label": "uses FilterState, status/category/source constants" }, + { "source": "file:src/lib/components/HistoryPanel.svelte", "target": "file:src/lib/stores/api.ts", "type": "imports", "label": "calls getHistory and restoreToVersion" }, + { "source": "file:src/lib/components/HistoryPanel.svelte", "target": "file:src/lib/types/index.ts", "type": "imports", "label": "uses HistoryEntry type" }, + { "source": "file:src/lib/components/HistoryPanel.svelte", "target": "file:src/lib/components/FieldDiff.svelte", "type": "imports", "label": "renders field changes" }, + { "source": "file:src/lib/components/InterviewStageForm.svelte", "target": "file:src/lib/types/index.ts", "type": "imports", "label": "uses InterviewStage and input types" }, + { "source": "file:src/lib/components/InterviewStageForm.svelte", "target": "file:src/lib/components/RatingInput.svelte", "type": "imports", "label": "performance rating" }, + { "source": "file:src/lib/components/InterviewStageItem.svelte", "target": "file:src/lib/types/index.ts", "type": "imports", "label": "uses InterviewStage type" }, + { "source": "file:src/lib/components/InterviewStageItem.svelte", "target": "file:src/lib/components/RatingDisplay.svelte", "type": "imports", "label": "performance rating stars" }, + { "source": "file:src/lib/components/InterviewStageList.svelte", "target": "file:src/lib/types/index.ts", "type": "imports", "label": "uses InterviewStage and input types" }, + { "source": "file:src/lib/components/InterviewStageList.svelte", "target": "file:src/lib/components/InterviewStageItem.svelte", "type": "imports", "label": "renders stage rows" }, + { "source": "file:src/lib/components/InterviewStageList.svelte", "target": "file:src/lib/components/InterviewStageForm.svelte", "type": "imports", "label": "add/edit form" }, + { "source": "file:src/lib/components/StatusBadge.svelte", "target": "file:src/lib/types/index.ts", "type": "imports", "label": "uses STATUS_LABELS, STATUS_COLORS" }, + { "source": "file:src/lib/types/index.test.ts", "target": "file:src/lib/types/index.ts", "type": "imports", "label": "tests ALL_STATUSES, STATUS_LABELS, STATUS_COLORS" }, + { "source": "file:src/lib/components/RatingInput.test.ts", "target": "file:src/lib/components/RatingInput.svelte", "type": "imports", "label": "tests RatingInput component" }, + { "source": "file:vite.config.ts", "target": "file:svelte.config.js", "type": "configures", "label": "uses sveltekit() plugin" }, + { "source": "file:src/app.html", "target": "file:src/routes/+layout.svelte", "type": "renders", "label": "mounts SvelteKit body" }, + { "source": "file:vite.config.ts", "target": "file:package.json", "type": "configures", "label": "defined in package" } + ], + "layers": [ + { + "id": "layer:entrypoints", + "name": "Entry Points & Shell", + "description": "HTML shell, global styles, root layout, and SvelteKit configuration that bootstrap the application.", + "nodeIds": [ + "file:src/app.html", + "file:src/app.css", + "file:src/routes/+layout.ts", + "file:src/routes/+layout.svelte" + ] + }, + { + "id": "layer:routes", + "name": "Route Pages", + "description": "SvelteKit +page.svelte files that correspond to URL routes: home list, application detail ([id]), and create new.", + "nodeIds": [ + "file:src/routes/+page.svelte", + "file:src/routes/applications/[id]/+page.svelte", + "file:src/routes/applications/new/+page.svelte" + ] + }, + { + "id": "layer:domain", + "name": "Domain Types & Constants", + "description": "TypeScript interfaces, union types, and display-helper constants that define the application's data model.", + "nodeIds": [ + "file:src/lib/types/index.ts", + "file:src/lib/types/index.test.ts" + ] + }, + { + "id": "layer:data", + "name": "Data Layer (Stores & API)", + "description": "The HTTP API client and the Svelte 5 runes-based reactive store that coordinate data fetching and mutations.", + "nodeIds": [ + "file:src/lib/stores/api.ts", + "file:src/lib/stores/applications.svelte.ts" + ] + }, + { + "id": "layer:feature-components", + "name": "Feature Components", + "description": "High-level components that implement major user-facing features: list card, detail panel, edit form, filter bar, history, and pagination.", + "nodeIds": [ + "file:src/lib/components/ApplicationCard.svelte", + "file:src/lib/components/ApplicationDetail.svelte", + "file:src/lib/components/ApplicationEdit.svelte", + "file:src/lib/components/ApplicationForm.svelte", + "file:src/lib/components/FilterBar.svelte", + "file:src/lib/components/HistoryPanel.svelte", + "file:src/lib/components/Pagination.svelte" + ] + }, + { + "id": "layer:interview-stage-components", + "name": "Interview Stage Components", + "description": "Components specifically for managing interview stages within an application: list container, individual item row, and add/edit form.", + "nodeIds": [ + "file:src/lib/components/InterviewStageList.svelte", + "file:src/lib/components/InterviewStageItem.svelte", + "file:src/lib/components/InterviewStageForm.svelte" + ] + }, + { + "id": "layer:primitive-components", + "name": "Primitive / UI Components", + "description": "Small, reusable leaf components with no business logic: rating input/display, status badge, URL field, confirm dialog, empty state, field diff.", + "nodeIds": [ + "file:src/lib/components/RatingInput.svelte", + "file:src/lib/components/RatingDisplay.svelte", + "file:src/lib/components/RatingInput.test.ts", + "file:src/lib/components/StatusBadge.svelte", + "file:src/lib/components/UrlFieldInput.svelte", + "file:src/lib/components/ConfirmDialog.svelte", + "file:src/lib/components/EmptyState.svelte", + "file:src/lib/components/FieldDiff.svelte" + ] + }, + { + "id": "layer:config", + "name": "Build Configuration", + "description": "Project config files: SvelteKit adapter config, Vite dev server and test config, and package.json.", + "nodeIds": [ + "file:svelte.config.js", + "file:vite.config.ts", + "file:package.json" + ] + } + ], + "tour": [ + { + "id": "tour:step-1", + "title": "HTML Shell & Global Styles", + "description": "Start at app.html, which is the single HTML page SvelteKit injects into. Then look at app.css to understand the Tailwind 4 setup and the custom design-system classes (.btn-primary, .input, .card) used throughout every component.", + "order": 1, + "nodeIds": ["file:src/app.html", "file:src/app.css"] + }, + { + "id": "tour:step-2", + "title": "Domain Types — the data model", + "description": "Read src/lib/types/index.ts before anything else. It defines every interface (Application, InterviewStage, FilterState, HistoryEntry) and all enum unions (ApplicationStatus, CompanyCategory, JobSource) plus display helpers. Every other file imports from here.", + "order": 2, + "nodeIds": ["file:src/lib/types/index.ts"] + }, + { + "id": "tour:step-3", + "title": "API Client", + "description": "src/lib/stores/api.ts is the HTTP boundary. It wraps fetch calls to /api (proxied to hono-api on port 5030). All CRUD for applications, interview stages, and history flows through this singleton. Note the 204 handling and error surfacing pattern.", + "order": 3, + "nodeIds": ["file:src/lib/stores/api.ts"] + }, + { + "id": "tour:step-4", + "title": "Application Store — Svelte 5 runes", + "description": "applications.svelte.ts shows the Svelte 5 factory-function store pattern using $state, $derived, and exported getters. It owns the paginated list, loading/error state, and filter state. The singleton `applicationStore` is shared across the home page and ApplicationEdit.", + "order": 4, + "nodeIds": ["file:src/lib/stores/applications.svelte.ts"] + }, + { + "id": "tour:step-5", + "title": "SvelteKit Routing & Layout", + "description": "src/routes/+layout.ts disables SSR (ssr=false) making this a pure SPA. +layout.svelte wraps all pages in the nav bar with the Svelte logo, app title, and dark-mode toggle (localStorage-backed with system-preference default).", + "order": 5, + "nodeIds": ["file:src/routes/+layout.ts", "file:src/routes/+layout.svelte"] + }, + { + "id": "tour:step-6", + "title": "Home Page — List View", + "description": "src/routes/+page.svelte is the main list page. It uses $effect to reload when filters change, renders FilterBar, the ApplicationCard grid, Pagination, EmptyState, and a delete ConfirmDialog. Archive/restore is handled inline.", + "order": 6, + "nodeIds": ["file:src/routes/+page.svelte", "file:src/lib/components/FilterBar.svelte", "file:src/lib/components/Pagination.svelte"] + }, + { + "id": "tour:step-7", + "title": "Application Card", + "description": "ApplicationCard.svelte is the list-item component. It displays a clickable card with company/position, StatusBadge, skill rating, interview stage progress, offer due-date urgency, and an overflow menu (Archive/Delete) that uses stopPropagation to prevent card click.", + "order": 7, + "nodeIds": ["file:src/lib/components/ApplicationCard.svelte", "file:src/lib/components/StatusBadge.svelte", "file:src/lib/components/RatingDisplay.svelte"] + }, + { + "id": "tour:step-8", + "title": "Create / Edit Application", + "description": "ApplicationEdit.svelte is the most complex component. It handles both create mode (no id) and edit mode (id from route params). Key patterns: snapshot-based isDirty tracking, beforeNavigate + window.beforeunload guards, status-to-date enforcement, and inline interview stage management with local stages in create mode.", + "order": 8, + "nodeIds": ["file:src/routes/applications/new/+page.svelte", "file:src/routes/applications/[id]/+page.svelte", "file:src/lib/components/ApplicationEdit.svelte"] + }, + { + "id": "tour:step-9", + "title": "Interview Stages", + "description": "Three components form the interview stage subsystem. InterviewStageList is the container (used in ApplicationDetail). InterviewStageItem renders one row with toggle-complete, edit, delete. InterviewStageForm handles add/edit. ApplicationEdit inlines these directly without the List wrapper.", + "order": 9, + "nodeIds": ["file:src/lib/components/InterviewStageList.svelte", "file:src/lib/components/InterviewStageItem.svelte", "file:src/lib/components/InterviewStageForm.svelte"] + }, + { + "id": "tour:step-10", + "title": "History Panel & Field Diff", + "description": "HistoryPanel.svelte is a fixed right slide-over that loads change history via api.getHistory. Each entry expands to show FieldDiff (red strikethrough → green new values). Non-latest entries offer restore. Launched from ApplicationEdit via the History button.", + "order": 10, + "nodeIds": ["file:src/lib/components/HistoryPanel.svelte", "file:src/lib/components/FieldDiff.svelte"] + }, + { + "id": "tour:step-11", + "title": "Primitive Components & Accessibility", + "description": "RatingInput implements an ARIA radiogroup with roving tabindex, hover preview, and keyboard activation — see its extensive test suite in RatingInput.test.ts. ConfirmDialog handles Escape/backdrop. UrlFieldInput adds an open-in-tab button for valid URLs.", + "order": 11, + "nodeIds": ["file:src/lib/components/RatingInput.svelte", "file:src/lib/components/RatingInput.test.ts", "file:src/lib/components/ConfirmDialog.svelte", "file:src/lib/components/UrlFieldInput.svelte"] + }, + { + "id": "tour:step-12", + "title": "Build & Dev Config", + "description": "vite.config.ts configures the dev server (port 3030) and the /api proxy to hono-api (port 5030). svelte.config.js sets up the $lib alias and vitePreprocess. package.json shows the exact dependency versions.", + "order": 12, + "nodeIds": ["file:vite.config.ts", "file:svelte.config.js", "file:package.json"] + } + ] +} diff --git a/svelte-ui/.understand-anything/meta.json b/svelte-ui/.understand-anything/meta.json new file mode 100644 index 00000000..344047f6 --- /dev/null +++ b/svelte-ui/.understand-anything/meta.json @@ -0,0 +1,47 @@ +{ + "version": "2.5.1", + "projectName": "job-tracker-svelte-ui", + "projectDescription": "SvelteKit 5 SPA for tracking job applications. Uses Svelte 5 runes ($state, $derived, $effect), Tailwind CSS 4, and proxies API calls to hono-api on port 5030.", + "languages": [ + "TypeScript", + "Svelte", + "CSS", + "HTML" + ], + "frameworks": [ + "SvelteKit", + "Svelte 5", + "Tailwind CSS 4", + "Vitest", + "Testing Library" + ], + "generatedAt": "2026-05-04T00:00:00.000Z", + "gitHash": "e5054096efec8165e7d4cfe11fc103f77ad2d98f", + "entryPoint": "src/routes/+layout.svelte", + "stats": { + "filesAnalyzed": 32, + "totalNodes": 32, + "totalEdges": 51, + "totalLayers": 8, + "tourSteps": 12, + "nodeTypes": { + "file": 29, + "config": 3 + }, + "edgeTypes": { + "imports": 44, + "renders": 4, + "configures": 3 + } + }, + "layers": [ + "Entry Points & Shell", + "Route Pages", + "Domain Types & Constants", + "Data Layer (Stores & API)", + "Feature Components", + "Interview Stage Components", + "Primitive / UI Components", + "Build Configuration" + ] +} diff --git a/tanstack-start-ui/.understand-anything/.understandignore b/tanstack-start-ui/.understand-anything/.understandignore new file mode 100644 index 00000000..6fc4c503 --- /dev/null +++ b/tanstack-start-ui/.understand-anything/.understandignore @@ -0,0 +1,6 @@ +# .understandignore — patterns for files/dirs to exclude from analysis +# Built-in defaults (always excluded): node_modules/, .git/, dist/, build/, *.lock + +# Exclude test output and static assets +test-results/ +public/ diff --git a/tanstack-start-ui/.understand-anything/knowledge-graph.json b/tanstack-start-ui/.understand-anything/knowledge-graph.json new file mode 100644 index 00000000..702ba72d --- /dev/null +++ b/tanstack-start-ui/.understand-anything/knowledge-graph.json @@ -0,0 +1,1807 @@ +{ + "nodes": [ + { + "id": "file:src/router.tsx", + "type": "file", + "name": "router.tsx", + "filePath": "src/router.tsx", + "summary": "Application entry point that creates the TanStack Router instance. Configures SSR dehydration/hydration of TanStack Query state using dehydrate/hydrate. Registers the router type globally via module augmentation. Uses file-based routeTree generated by TanStack Router.", + "tags": [ + "router", + "ssr", + "entry-point", + "tanstack-router", + "react-query" + ], + "complexity": "moderate" + }, + { + "id": "file:src/routeTree.gen.ts", + "type": "file", + "name": "routeTree.gen.ts", + "filePath": "src/routeTree.gen.ts", + "summary": "Auto-generated route tree file produced by TanStack Router's file-based routing system. Aggregates all route modules into a single tree structure consumed by router.tsx. Should not be manually edited.", + "tags": [ + "generated", + "routes", + "tanstack-router", + "file-based-routing" + ], + "complexity": "moderate" + }, + { + "id": "file:src/routes/__root.tsx", + "type": "file", + "name": "__root.tsx", + "filePath": "src/routes/__root.tsx", + "summary": "Root route that wraps the entire app. Sets up HTML document structure with HeadContent and Scripts for SSR, includes QueryClientProvider, global Header, and an Outlet for child routes. Contains inline dark mode script to prevent flash of unstyled content (FOUC) during SSR hydration.", + "tags": [ + "root-route", + "ssr", + "dark-mode", + "layout", + "query-client" + ], + "complexity": "moderate" + }, + { + "id": "file:src/routes/index.tsx", + "type": "file", + "name": "index.tsx", + "filePath": "src/routes/index.tsx", + "summary": "Home page route (/) showing the paginated list of job applications. Uses loader to prefetch data via server function. Manages filter state (useFilters), sort state (useSorting), and pagination. Renders FilterBar, ApplicationList, and ImportModal components.", + "tags": [ + "route", + "list-view", + "pagination", + "filters", + "server-prefetch" + ], + "complexity": "moderate" + }, + { + "id": "file:src/routes/applications/$id.tsx", + "type": "file", + "name": "$id.tsx", + "filePath": "src/routes/applications/$id.tsx", + "summary": "Dynamic route for viewing/editing an existing application (/applications/:id). Uses SSR loader to fetch application data via server function. Uses useHydrated to prevent hydration mismatch. Renders ApplicationEdit component with fetched initialApplication data.", + "tags": [ + "route", + "dynamic-route", + "ssr-loader", + "edit-view", + "hydration" + ], + "complexity": "moderate" + }, + { + "id": "file:src/routes/applications/new.tsx", + "type": "file", + "name": "new.tsx", + "filePath": "src/routes/applications/new.tsx", + "summary": "Route for creating a new application (/applications/new). Uses useHydrated to delay rendering until client is ready. Renders ApplicationEdit without initialApplication data, triggering create mode.", + "tags": [ + "route", + "create-view", + "hydration" + ], + "complexity": "moderate" + }, + { + "id": "file:src/types/application.ts", + "type": "file", + "name": "application.ts", + "filePath": "src/types/application.ts", + "summary": "Central TypeScript type definitions for the application domain. Defines ApplicationStatus, CompanyCategory, and JobSource union types. Defines interfaces for Application, InterviewStage, CreateApplicationInput, UpdateApplicationInput, CreateInterviewStageInput, PaginatedApplications, FilterState, SortState, ImportResult, ErrorResponse, FieldChange, HistoryEntry, and PaginatedHistoryResponse.", + "tags": [ + "types", + "domain-model", + "typescript", + "interfaces" + ], + "complexity": "moderate" + }, + { + "id": "file:src/lib/constants.ts", + "type": "file", + "name": "constants.ts", + "filePath": "src/lib/constants.ts", + "summary": "Application-wide constants. Exports APPLICATION_STATUSES, COMPANY_CATEGORIES, JOB_SOURCES arrays for select options, STATUS_COLORS mapping for badge styling, and DEFAULT_INTERVIEW_STAGES list for pre-populating interview pipelines.", + "tags": [ + "constants", + "configuration", + "statuses", + "categories" + ], + "complexity": "moderate" + }, + { + "id": "file:src/lib/queryClient.ts", + "type": "file", + "name": "queryClient.ts", + "filePath": "src/lib/queryClient.ts", + "summary": "Singleton TanStack Query client instance shared across client and server. Configured with staleTime: 0 (always fresh) and retry: 1. Used in router.tsx for SSR dehydration and in queries for cache invalidation.", + "tags": [ + "react-query", + "query-client", + "singleton", + "ssr" + ], + "complexity": "moderate" + }, + { + "id": "file:src/lib/utils.ts", + "type": "file", + "name": "utils.ts", + "filePath": "src/lib/utils.ts", + "summary": "Pure utility functions. Exports cn() for className merging, formatDate() for locale date formatting, formatCurrency() / formatSalaryRange() for USD display, getDaysUntil() and isOverdue() for offer deadline tracking, and getTodayDate() for YYYY-MM-DD formatted today.", + "tags": [ + "utilities", + "formatting", + "date", + "currency", + "classnames" + ], + "complexity": "moderate" + }, + { + "id": "file:src/lib/utils.test.ts", + "type": "file", + "name": "utils.test.ts", + "filePath": "src/lib/utils.test.ts", + "summary": "Vitest unit tests for the utility functions in utils.ts. Tests formatDate, formatCurrency, formatSalaryRange, getDaysUntil, isOverdue, and getTodayDate functions.", + "tags": [ + "tests", + "vitest", + "unit-tests", + "utilities" + ], + "complexity": "moderate" + }, + { + "id": "file:src/hooks/useFilters.ts", + "type": "file", + "name": "useFilters.ts", + "filePath": "src/hooks/useFilters.ts", + "summary": "Custom React hook managing application list filter state. Tracks status[], companyCategory, jobSource, skillsMatchMin, and includeArchived via useState. Exposes individual setters (memoized with useCallback), clearFilters(), hasActiveFilters boolean, and activeFilterCount number.", + "tags": [ + "hook", + "filters", + "state-management", + "react" + ], + "complexity": "moderate" + }, + { + "id": "file:src/hooks/useSorting.ts", + "type": "file", + "name": "useSorting.ts", + "filePath": "src/hooks/useSorting.ts", + "summary": "Custom React hook managing application list sort state. Tracks sortBy (dateApplied | companyName | updatedAt) and sortDir (asc | desc). Exposes setSortBy, setSortDir, and toggleSortDir helpers.", + "tags": [ + "hook", + "sorting", + "state-management", + "react" + ], + "complexity": "moderate" + }, + { + "id": "file:src/hooks/useHydrated.ts", + "type": "file", + "name": "useHydrated.ts", + "filePath": "src/hooks/useHydrated.ts", + "summary": "Custom React hook that returns true only after React hydration completes (useEffect fires). Used to delay rendering interactive content in SSR routes, ensuring E2E tests can rely on hydration before interacting with the page.", + "tags": [ + "hook", + "ssr", + "hydration", + "react" + ], + "complexity": "moderate" + }, + { + "id": "file:src/queries/queryKeys.ts", + "type": "file", + "name": "queryKeys.ts", + "filePath": "src/queries/queryKeys.ts", + "summary": "Centralized TanStack Query key factory (applicationKeys). Defines hierarchical key structure: all > lists > list(params) and all > details > detail(id) and all > histories > history(id). Used for cache invalidation throughout mutations.", + "tags": [ + "react-query", + "query-keys", + "cache-invalidation" + ], + "complexity": "moderate" + }, + { + "id": "file:src/queries/applicationQueries.ts", + "type": "file", + "name": "applicationQueries.ts", + "filePath": "src/queries/applicationQueries.ts", + "summary": "TanStack Query hooks for reading application data. Exports useApplications(params) for paginated list, useApplication(id) for single application (with staleTime:60s and refetchOnWindowFocus:false to avoid clobbering edits), and useApplicationHistory(id, page, limit) for history entries.", + "tags": [ + "react-query", + "queries", + "data-fetching", + "hooks" + ], + "complexity": "moderate" + }, + { + "id": "file:src/queries/applicationMutations.ts", + "type": "file", + "name": "applicationMutations.ts", + "filePath": "src/queries/applicationMutations.ts", + "summary": "TanStack Query mutation hooks for write operations. Exports useCreateApplication, useUpdateApplication, useDeleteApplication, useArchiveApplication, useRestoreApplication, useRestoreToVersion, useImportApplications, useCreateStage, useUpdateStage, useDeleteStage. Each invalidates appropriate query cache keys on success.", + "tags": [ + "react-query", + "mutations", + "write-operations", + "cache-invalidation" + ], + "complexity": "moderate" + }, + { + "id": "file:src/services/api.ts", + "type": "file", + "name": "api.ts", + "filePath": "src/services/api.ts", + "summary": "Client-side HTTP API service layer. Makes fetch calls to /api/* (proxied to FastAPI at port 5160). Implements ApiError class, handleResponse() for error handling and 204 support, buildQueryString() for URL param building. Exports functions for all CRUD operations, interview stage operations, history operations, and CSV import/export URLs.", + "tags": [ + "api-client", + "http", + "fetch", + "error-handling", + "client-side" + ], + "complexity": "moderate" + }, + { + "id": "file:src/server/api.ts", + "type": "file", + "name": "api.ts", + "filePath": "src/server/api.ts", + "summary": "Server-side HTTP utility for TanStack Start server functions. Uses FASTAPI_URL env var (default: http://localhost:5160) for direct server-to-server calls. Implements ServerApiError class and serverFetch() for JSON requests. Also exports buildQueryString() used by server function handlers.", + "tags": [ + "server", + "http", + "ssr", + "server-function", + "fastapi" + ], + "complexity": "moderate" + }, + { + "id": "file:src/server/applications.ts", + "type": "file", + "name": "applications.ts", + "filePath": "src/server/applications.ts", + "summary": "TanStack Start server functions for application data. Uses createServerFn to define server-side data fetching that runs during SSR. Exports fetchApplications (GET with list params) and fetchApplication (GET single by id). Called in route loaders for SSR prefetching.", + "tags": [ + "server-function", + "ssr", + "tanstack-start", + "data-loading" + ], + "complexity": "moderate" + }, + { + "id": "file:vite.config.ts", + "type": "config", + "name": "vite.config.ts", + "filePath": "vite.config.ts", + "summary": "Vite build configuration. Sets dev server port to 3040. Configures /api proxy to FastAPI at localhost:5160 with path rewriting (strips /api prefix, normalizes /applications to /applications/ to avoid FastAPI 307 redirects). Plugins: tanstackStart, react (must come after tanstackStart), vite-tsconfig-paths.", + "tags": [ + "config", + "vite", + "proxy", + "build", + "dev-server" + ], + "complexity": "moderate" + }, + { + "id": "file:tsconfig.json", + "type": "config", + "name": "tsconfig.json", + "filePath": "tsconfig.json", + "summary": "TypeScript configuration. Target ES2022, strict mode with strictNullChecks. Module resolution: bundler. JSX: react-jsx. Path alias @/* -> src/*. noEmit: true (Vite handles compilation). Includes only src/ directory.", + "tags": [ + "config", + "typescript", + "strict-mode", + "path-aliases" + ], + "complexity": "moderate" + }, + { + "id": "file:package.json", + "type": "config", + "name": "package.json", + "filePath": "package.json", + "summary": "Package manifest for tanstack-start-ui. Dependencies: @tanstack/react-query, @tanstack/react-router, @tanstack/react-start, react 19, react-dom. Dev: TypeScript, Vite 7, Vitest 4, Tailwind 4, ESLint 10, @testing-library/react.", + "tags": [ + "config", + "package", + "dependencies", + "npm" + ], + "complexity": "moderate" + }, + { + "id": "file:src/components/applications/ApplicationCard.tsx", + "type": "file", + "name": "ApplicationCard.tsx", + "filePath": "src/components/applications/ApplicationCard.tsx", + "summary": "Card component for displaying a single application in the list. Shows company name, status badge, position title, date applied, category, skills match rating, interview stage progress, and offer deadline countdown. Has a context menu (three-dot button) for archive/restore and delete actions with a fixed-positioned dropdown. Uses ConfirmDialog for delete confirmation.", + "tags": [ + "component", + "application", + "list-item", + "card", + "context-menu" + ], + "complexity": "moderate" + }, + { + "id": "file:src/components/applications/ApplicationEdit.tsx", + "type": "file", + "name": "ApplicationEdit.tsx", + "filePath": "src/components/applications/ApplicationEdit.tsx", + "summary": "Large form component for creating and editing job applications. Handles both create (no applicationId) and edit (with applicationId) modes. Manages FormState locally, populates from Application data, tracks isDirty state, uses useBlocker for unsaved changes navigation guard. Includes fields for all application properties plus interview stages management and history panel. Shows delete confirmation dialog.", + "tags": [ + "component", + "form", + "create", + "edit", + "application", + "interview-stages", + "navigation-guard" + ], + "complexity": "moderate" + }, + { + "id": "file:src/components/applications/ApplicationList.tsx", + "type": "file", + "name": "ApplicationList.tsx", + "filePath": "src/components/applications/ApplicationList.tsx", + "summary": "Container component that renders the list of ApplicationCard items. Handles loading skeleton state (3 pulse placeholders), empty state with two variants (no-filters empty and filtered-empty with ClearFilters action), and pagination via Pagination component. Delegates archive/restore/delete to parent via callbacks.", + "tags": [ + "component", + "list", + "loading-state", + "empty-state", + "pagination" + ], + "complexity": "moderate" + }, + { + "id": "file:src/components/applications/FieldDiff.tsx", + "type": "file", + "name": "FieldDiff.tsx", + "filePath": "src/components/applications/FieldDiff.tsx", + "summary": "Presentational component for displaying field-level changes in history entries. Renders each FieldChange as label with old value (red strikethrough) and new value (green). formatValue() handles null, boolean, array, object, and primitive values.", + "tags": [ + "component", + "history", + "diff", + "presentational" + ], + "complexity": "moderate" + }, + { + "id": "file:src/components/applications/FilterBar.tsx", + "type": "file", + "name": "FilterBar.tsx", + "filePath": "src/components/applications/FilterBar.tsx", + "summary": "Filter and sort controls toolbar for the application list. Contains Select dropdowns for status, category, source, skills match, Include Archived checkbox, and Clear button. Also shows result count, Sort controls (sortBy dropdown + direction toggle button), and Import/Export/Template CSV buttons.", + "tags": [ + "component", + "filters", + "sorting", + "toolbar", + "csv" + ], + "complexity": "moderate" + }, + { + "id": "file:src/components/applications/HistoryPanel.tsx", + "type": "file", + "name": "HistoryPanel.tsx", + "filePath": "src/components/applications/HistoryPanel.tsx", + "summary": "Sliding side panel (fixed right-side drawer) showing application change history. Fetches history via useApplicationHistory. Displays entries with relative timestamps, expand/collapse for field diffs, and Restore button (skipped for current/latest entry). Handles Escape key to close. Uses FieldDiff for change display.", + "tags": [ + "component", + "history", + "panel", + "drawer", + "restore" + ], + "complexity": "moderate" + }, + { + "id": "file:src/components/applications/ImportModal.tsx", + "type": "file", + "name": "ImportModal.tsx", + "filePath": "src/components/applications/ImportModal.tsx", + "summary": "Modal dialog for importing job applications from a CSV file. File input accepts .csv files, triggers useImportApplications mutation. After import shows results summary (imported/skipped/errors counts) with per-row error details. Links to sample CSV template download.", + "tags": [ + "component", + "import", + "csv", + "modal", + "file-upload" + ], + "complexity": "moderate" + }, + { + "id": "file:src/components/applications/index.ts", + "type": "file", + "name": "index.ts", + "filePath": "src/components/applications/index.ts", + "summary": "Barrel export file for the applications component directory. Re-exports ApplicationCard, ApplicationEdit, ApplicationList, FieldDiff, FilterBar, HistoryPanel, and ImportModal for convenient importing.", + "tags": [ + "barrel-export", + "module" + ], + "complexity": "moderate" + }, + { + "id": "file:src/components/common/Header.tsx", + "type": "file", + "name": "Header.tsx", + "filePath": "src/components/common/Header.tsx", + "summary": "Application header component. Displays Python-logo SVG, app title with stack description. Includes dark mode toggle (persists to localStorage, syncs with system preference). Add Application button navigates to /applications/new. Handles hydration-safe rendering to prevent FOUC.", + "tags": [ + "component", + "header", + "dark-mode", + "navigation", + "layout" + ], + "complexity": "moderate" + }, + { + "id": "file:src/components/common/index.ts", + "type": "file", + "name": "index.ts", + "filePath": "src/components/common/index.ts", + "summary": "Barrel export file for the common component directory.", + "tags": [ + "barrel-export", + "module" + ], + "complexity": "moderate" + }, + { + "id": "file:src/components/interviews/InlineInterviewStageForm.tsx", + "type": "file", + "name": "InlineInterviewStageForm.tsx", + "filePath": "src/components/interviews/InlineInterviewStageForm.tsx", + "summary": "Inline form component for adding or editing interview stages directly within the ApplicationEdit form (used instead of a modal, rendered inline). Collects name, order, isCompleted, completedDate, notes, and performanceRating fields.", + "tags": [ + "component", + "form", + "interview-stages", + "inline" + ], + "complexity": "moderate" + }, + { + "id": "file:src/components/interviews/InterviewStageForm.tsx", + "type": "file", + "name": "InterviewStageForm.tsx", + "filePath": "src/components/interviews/InterviewStageForm.tsx", + "summary": "Modal-based form for adding or editing interview stages. Opens as a dialog overlay. Handles both create (no stage prop) and edit (with stage prop) modes. Fields: name, order, isCompleted checkbox, completedDate, performanceRating, notes.", + "tags": [ + "component", + "form", + "interview-stages", + "modal" + ], + "complexity": "moderate" + }, + { + "id": "file:src/components/interviews/InterviewStageItem.tsx", + "type": "file", + "name": "InterviewStageItem.tsx", + "filePath": "src/components/interviews/InterviewStageItem.tsx", + "summary": "Individual interview stage item component. Shows stage name with completion checkbox, completed date, performance rating, and notes icon. Expandable to reveal full notes and Edit/Remove buttons. Delete uses ConfirmDialog. Toggle complete sets/clears completedDate to today.", + "tags": [ + "component", + "interview-stages", + "list-item", + "expandable" + ], + "complexity": "moderate" + }, + { + "id": "file:src/components/interviews/InterviewStageList.tsx", + "type": "file", + "name": "InterviewStageList.tsx", + "filePath": "src/components/interviews/InterviewStageList.tsx", + "summary": "Container component managing the list of interview stages. Shows completion progress bar (completedCount/total). Renders sorted InterviewStageItems. Empty state has 'Add Default Stages' button (pre-populates 6 common stages). Has 'Add Stage' button toggling InterviewStageForm modal. Handles async add/update/remove via callbacks.", + "tags": [ + "component", + "interview-stages", + "list", + "progress-bar" + ], + "complexity": "moderate" + }, + { + "id": "file:src/components/interviews/index.ts", + "type": "file", + "name": "index.ts", + "filePath": "src/components/interviews/index.ts", + "summary": "Barrel export file for the interviews component directory. Re-exports InlineInterviewStageForm, InterviewStageForm, InterviewStageItem, and InterviewStageList.", + "tags": [ + "barrel-export", + "module" + ], + "complexity": "moderate" + }, + { + "id": "file:src/components/ui/Badge.tsx", + "type": "file", + "name": "Badge.tsx", + "filePath": "src/components/ui/Badge.tsx", + "summary": "Status badge component. Displays ApplicationStatus as colored pill badge using STATUS_COLORS mapping. Supports 'sm' and 'md' sizes. Looks up human-readable label from APPLICATION_STATUSES.", + "tags": [ + "component", + "ui", + "badge", + "status" + ], + "complexity": "moderate" + }, + { + "id": "file:src/components/ui/Button.tsx", + "type": "file", + "name": "Button.tsx", + "filePath": "src/components/ui/Button.tsx", + "summary": "Reusable button component. Variants: primary (blue), secondary (gray), danger (red), ghost (transparent). Sizes: sm, md, lg. Uses forwardRef. Applies base styles for focus ring, disabled opacity.", + "tags": [ + "component", + "ui", + "button", + "primitive" + ], + "complexity": "moderate" + }, + { + "id": "file:src/components/ui/Card.tsx", + "type": "file", + "name": "Card.tsx", + "filePath": "src/components/ui/Card.tsx", + "summary": "Card container component with optional hover shadow. Uses forwardRef. Also exports CardHeader, CardContent, CardFooter sub-components for structured layout. All use Tailwind dark mode variants.", + "tags": [ + "component", + "ui", + "card", + "container", + "primitive" + ], + "complexity": "moderate" + }, + { + "id": "file:src/components/ui/Checkbox.tsx", + "type": "file", + "name": "Checkbox.tsx", + "filePath": "src/components/ui/Checkbox.tsx", + "summary": "Labeled checkbox form control. Wraps HTML input[type=checkbox] with accessible label. Supports all InputHTMLAttributes.", + "tags": [ + "component", + "ui", + "form-control", + "checkbox" + ], + "complexity": "moderate" + }, + { + "id": "file:src/components/ui/EmptyState.tsx", + "type": "file", + "name": "EmptyState.tsx", + "filePath": "src/components/ui/EmptyState.tsx", + "summary": "Empty state display component. Shows centered icon, title text, description text, and optional action element (typically a Button). Used by ApplicationList for no-results scenarios.", + "tags": [ + "component", + "ui", + "empty-state", + "feedback" + ], + "complexity": "moderate" + }, + { + "id": "file:src/components/ui/Input.tsx", + "type": "file", + "name": "Input.tsx", + "filePath": "src/components/ui/Input.tsx", + "summary": "Form input components. Exports Input (single-line text input) and TextArea (multi-line). Both support label, error message display, and dark mode. Use forwardRef and pass all HTMLInputAttributes through.", + "tags": [ + "component", + "ui", + "form-control", + "input", + "textarea" + ], + "complexity": "moderate" + }, + { + "id": "file:src/components/ui/Modal.tsx", + "type": "file", + "name": "Modal.tsx", + "filePath": "src/components/ui/Modal.tsx", + "summary": "Modal dialog component using React portals (renders to document.body). Supports sm/md/lg/xl sizes. Closes on backdrop click or Escape key. Locks body scroll when open. Also exports ConfirmDialog (builds on Modal) for destructive action confirmations.", + "tags": [ + "component", + "ui", + "modal", + "dialog", + "portal", + "confirm" + ], + "complexity": "moderate" + }, + { + "id": "file:src/components/ui/Pagination.tsx", + "type": "file", + "name": "Pagination.tsx", + "filePath": "src/components/ui/Pagination.tsx", + "summary": "Pagination controls component. Shows previous/next buttons and numbered page buttons with ellipsis for large page counts (delta=2 windowing). Returns null for single-page results.", + "tags": [ + "component", + "ui", + "pagination", + "navigation" + ], + "complexity": "moderate" + }, + { + "id": "file:src/components/ui/Rating.tsx", + "type": "file", + "name": "Rating.tsx", + "filePath": "src/components/ui/Rating.tsx", + "summary": "Rating components for skills match and interview performance. Exports RatingInput (interactive 1-5 star picker) and RatingDisplay (read-only star display). Supports 'sm' and 'md' sizes.", + "tags": [ + "component", + "ui", + "rating", + "stars", + "interactive" + ], + "complexity": "moderate" + }, + { + "id": "file:src/components/ui/Select.tsx", + "type": "file", + "name": "Select.tsx", + "filePath": "src/components/ui/Select.tsx", + "summary": "Select dropdown form control. Takes label, error, options array ({value, label}), and optional placeholder. Uses forwardRef. Supports dark mode. Error state shows red border and message.", + "tags": [ + "component", + "ui", + "form-control", + "select", + "dropdown" + ], + "complexity": "moderate" + }, + { + "id": "file:src/components/ui/UrlFieldInput.tsx", + "type": "file", + "name": "UrlFieldInput.tsx", + "filePath": "src/components/ui/UrlFieldInput.tsx", + "summary": "Specialized URL input component. Extends Input with a clickable external link icon button that opens the URL in a new tab when the field has a valid URL value.", + "tags": [ + "component", + "ui", + "input", + "url", + "specialized" + ], + "complexity": "moderate" + }, + { + "id": "file:src/components/ui/index.ts", + "type": "file", + "name": "index.ts", + "filePath": "src/components/ui/index.ts", + "summary": "Barrel export for all UI primitive components: Badge, Button, Card, CardHeader, CardContent, CardFooter, Checkbox, EmptyState, Input, TextArea, Modal, ConfirmDialog, Pagination, RatingInput, RatingDisplay, Select, UrlFieldInput.", + "tags": [ + "barrel-export", + "module", + "ui" + ], + "complexity": "moderate" + }, + { + "id": "file:src/index.css", + "type": "file", + "name": "index.css", + "filePath": "src/index.css", + "summary": "Global CSS entry point. Imports Tailwind CSS 4.x using @import 'tailwindcss'. Defines CSS custom properties for primary color scale and utility classes. Sets up dark mode support via .dark class on html element.", + "tags": [ + "styles", + "css", + "tailwind", + "dark-mode", + "global" + ], + "complexity": "moderate" + } + ], + "edges": [ + { + "source": "file:src/router.tsx", + "target": "file:src/routeTree.gen.ts", + "type": "imports", + "label": "imports routeTree" + }, + { + "source": "file:src/router.tsx", + "target": "file:src/lib/queryClient.ts", + "type": "imports", + "label": "imports queryClient" + }, + { + "source": "file:src/routes/__root.tsx", + "target": "file:src/lib/queryClient.ts", + "type": "imports", + "label": "imports queryClient" + }, + { + "source": "file:src/routes/__root.tsx", + "target": "file:src/components/common/Header.tsx", + "type": "imports", + "label": "imports Header" + }, + { + "source": "file:src/routes/index.tsx", + "target": "file:src/hooks/useFilters.ts", + "type": "imports", + "label": "imports useFilters" + }, + { + "source": "file:src/routes/index.tsx", + "target": "file:src/hooks/useSorting.ts", + "type": "imports", + "label": "imports useSorting" + }, + { + "source": "file:src/routes/index.tsx", + "target": "file:src/queries/applicationQueries.ts", + "type": "imports", + "label": "imports useApplications" + }, + { + "source": "file:src/routes/index.tsx", + "target": "file:src/queries/applicationMutations.ts", + "type": "imports", + "label": "imports mutations" + }, + { + "source": "file:src/routes/index.tsx", + "target": "file:src/components/applications/ApplicationList.tsx", + "type": "imports", + "label": "imports ApplicationList" + }, + { + "source": "file:src/routes/index.tsx", + "target": "file:src/components/applications/FilterBar.tsx", + "type": "imports", + "label": "imports FilterBar" + }, + { + "source": "file:src/routes/index.tsx", + "target": "file:src/components/applications/ImportModal.tsx", + "type": "imports", + "label": "imports ImportModal" + }, + { + "source": "file:src/routes/index.tsx", + "target": "file:src/server/applications.ts", + "type": "imports", + "label": "imports fetchApplications (server fn)" + }, + { + "source": "file:src/routes/index.tsx", + "target": "file:src/lib/queryClient.ts", + "type": "imports", + "label": "imports queryClient for prefetch" + }, + { + "source": "file:src/routes/index.tsx", + "target": "file:src/queries/queryKeys.ts", + "type": "imports", + "label": "imports applicationKeys" + }, + { + "source": "file:src/routes/applications/$id.tsx", + "target": "file:src/components/applications/ApplicationEdit.tsx", + "type": "imports", + "label": "imports ApplicationEdit" + }, + { + "source": "file:src/routes/applications/$id.tsx", + "target": "file:src/server/applications.ts", + "type": "imports", + "label": "imports fetchApplication (server fn)" + }, + { + "source": "file:src/routes/applications/$id.tsx", + "target": "file:src/hooks/useHydrated.ts", + "type": "imports", + "label": "imports useHydrated" + }, + { + "source": "file:src/routes/applications/new.tsx", + "target": "file:src/components/applications/ApplicationEdit.tsx", + "type": "imports", + "label": "imports ApplicationEdit" + }, + { + "source": "file:src/routes/applications/new.tsx", + "target": "file:src/hooks/useHydrated.ts", + "type": "imports", + "label": "imports useHydrated" + }, + { + "source": "file:src/lib/constants.ts", + "target": "file:src/types/application.ts", + "type": "imports", + "label": "imports type aliases" + }, + { + "source": "file:src/routeTree.gen.ts", + "target": "file:src/routes/__root.tsx", + "type": "imports", + "label": "aggregates root route" + }, + { + "source": "file:src/routeTree.gen.ts", + "target": "file:src/routes/index.tsx", + "type": "imports", + "label": "aggregates index route" + }, + { + "source": "file:src/routeTree.gen.ts", + "target": "file:src/routes/applications/$id.tsx", + "type": "imports", + "label": "aggregates $id route" + }, + { + "source": "file:src/routeTree.gen.ts", + "target": "file:src/routes/applications/new.tsx", + "type": "imports", + "label": "aggregates new route" + }, + { + "source": "file:src/hooks/useFilters.ts", + "target": "file:src/types/application.ts", + "type": "imports", + "label": "imports FilterState, ApplicationStatus, etc." + }, + { + "source": "file:src/hooks/useSorting.ts", + "target": "file:src/types/application.ts", + "type": "imports", + "label": "imports SortState" + }, + { + "source": "file:src/queries/queryKeys.ts", + "target": "file:src/types/application.ts", + "type": "imports", + "label": "no direct import, used by queries" + }, + { + "source": "file:src/queries/applicationQueries.ts", + "target": "file:src/services/api.ts", + "type": "imports", + "label": "imports api functions" + }, + { + "source": "file:src/queries/applicationQueries.ts", + "target": "file:src/queries/queryKeys.ts", + "type": "imports", + "label": "imports applicationKeys" + }, + { + "source": "file:src/queries/applicationQueries.ts", + "target": "file:src/types/application.ts", + "type": "imports", + "label": "imports ListApplicationsParams" + }, + { + "source": "file:src/queries/applicationMutations.ts", + "target": "file:src/services/api.ts", + "type": "imports", + "label": "imports api functions" + }, + { + "source": "file:src/queries/applicationMutations.ts", + "target": "file:src/queries/queryKeys.ts", + "type": "imports", + "label": "imports applicationKeys" + }, + { + "source": "file:src/queries/applicationMutations.ts", + "target": "file:src/types/application.ts", + "type": "imports", + "label": "imports input types" + }, + { + "source": "file:src/services/api.ts", + "target": "file:src/types/application.ts", + "type": "imports", + "label": "imports domain types" + }, + { + "source": "file:src/server/api.ts", + "target": "file:src/types/application.ts", + "type": "imports", + "label": "imports ErrorResponse" + }, + { + "source": "file:src/server/applications.ts", + "target": "file:src/server/api.ts", + "type": "imports", + "label": "imports serverFetch, buildQueryString" + }, + { + "source": "file:src/server/applications.ts", + "target": "file:src/types/application.ts", + "type": "imports", + "label": "imports domain types" + }, + { + "source": "file:src/lib/utils.test.ts", + "target": "file:src/lib/utils.ts", + "type": "imports", + "label": "tests utils functions" + }, + { + "source": "file:src/components/applications/ApplicationCard.tsx", + "target": "file:src/types/application.ts", + "type": "imports", + "label": "imports Application type" + }, + { + "source": "file:src/components/applications/ApplicationCard.tsx", + "target": "file:src/components/ui/Card.tsx", + "type": "imports", + "label": "imports Card" + }, + { + "source": "file:src/components/applications/ApplicationCard.tsx", + "target": "file:src/components/ui/Badge.tsx", + "type": "imports", + "label": "imports Badge" + }, + { + "source": "file:src/components/applications/ApplicationCard.tsx", + "target": "file:src/components/ui/Rating.tsx", + "type": "imports", + "label": "imports RatingDisplay" + }, + { + "source": "file:src/components/applications/ApplicationCard.tsx", + "target": "file:src/components/ui/Modal.tsx", + "type": "imports", + "label": "imports ConfirmDialog" + }, + { + "source": "file:src/components/applications/ApplicationCard.tsx", + "target": "file:src/lib/utils.ts", + "type": "imports", + "label": "imports formatDate, getDaysUntil, isOverdue, cn" + }, + { + "source": "file:src/components/applications/ApplicationCard.tsx", + "target": "file:src/lib/constants.ts", + "type": "imports", + "label": "imports COMPANY_CATEGORIES" + }, + { + "source": "file:src/components/applications/ApplicationEdit.tsx", + "target": "file:src/types/application.ts", + "type": "imports", + "label": "imports domain types" + }, + { + "source": "file:src/components/applications/ApplicationEdit.tsx", + "target": "file:src/lib/constants.ts", + "type": "imports", + "label": "imports STATUS/CATEGORY/SOURCE options" + }, + { + "source": "file:src/components/applications/ApplicationEdit.tsx", + "target": "file:src/lib/utils.ts", + "type": "imports", + "label": "imports getTodayDate" + }, + { + "source": "file:src/components/applications/ApplicationEdit.tsx", + "target": "file:src/queries/applicationQueries.ts", + "type": "imports", + "label": "imports useApplication" + }, + { + "source": "file:src/components/applications/ApplicationEdit.tsx", + "target": "file:src/queries/applicationMutations.ts", + "type": "imports", + "label": "imports all write mutations" + }, + { + "source": "file:src/components/applications/ApplicationEdit.tsx", + "target": "file:src/components/ui/Button.tsx", + "type": "imports", + "label": "imports Button" + }, + { + "source": "file:src/components/applications/ApplicationEdit.tsx", + "target": "file:src/components/ui/Rating.tsx", + "type": "imports", + "label": "imports RatingInput" + }, + { + "source": "file:src/components/applications/ApplicationEdit.tsx", + "target": "file:src/components/ui/Modal.tsx", + "type": "imports", + "label": "imports ConfirmDialog" + }, + { + "source": "file:src/components/applications/ApplicationEdit.tsx", + "target": "file:src/components/ui/UrlFieldInput.tsx", + "type": "imports", + "label": "imports UrlFieldInput" + }, + { + "source": "file:src/components/applications/ApplicationEdit.tsx", + "target": "file:src/components/interviews/InterviewStageItem.tsx", + "type": "imports", + "label": "imports InterviewStageItem" + }, + { + "source": "file:src/components/applications/ApplicationEdit.tsx", + "target": "file:src/components/interviews/InlineInterviewStageForm.tsx", + "type": "imports", + "label": "imports InlineInterviewStageForm" + }, + { + "source": "file:src/components/applications/ApplicationEdit.tsx", + "target": "file:src/components/applications/HistoryPanel.tsx", + "type": "imports", + "label": "imports HistoryPanel" + }, + { + "source": "file:src/components/applications/ApplicationList.tsx", + "target": "file:src/types/application.ts", + "type": "imports", + "label": "imports Application type" + }, + { + "source": "file:src/components/applications/ApplicationList.tsx", + "target": "file:src/components/applications/ApplicationCard.tsx", + "type": "imports", + "label": "imports ApplicationCard" + }, + { + "source": "file:src/components/applications/ApplicationList.tsx", + "target": "file:src/components/ui/EmptyState.tsx", + "type": "imports", + "label": "imports EmptyState" + }, + { + "source": "file:src/components/applications/ApplicationList.tsx", + "target": "file:src/components/ui/Button.tsx", + "type": "imports", + "label": "imports Button" + }, + { + "source": "file:src/components/applications/ApplicationList.tsx", + "target": "file:src/components/ui/Pagination.tsx", + "type": "imports", + "label": "imports Pagination" + }, + { + "source": "file:src/components/applications/FilterBar.tsx", + "target": "file:src/types/application.ts", + "type": "imports", + "label": "imports filter/sort types" + }, + { + "source": "file:src/components/applications/FilterBar.tsx", + "target": "file:src/components/ui/Button.tsx", + "type": "imports", + "label": "imports Button" + }, + { + "source": "file:src/components/applications/FilterBar.tsx", + "target": "file:src/components/ui/Select.tsx", + "type": "imports", + "label": "imports Select" + }, + { + "source": "file:src/components/applications/FilterBar.tsx", + "target": "file:src/components/ui/Checkbox.tsx", + "type": "imports", + "label": "imports Checkbox" + }, + { + "source": "file:src/components/applications/FilterBar.tsx", + "target": "file:src/lib/constants.ts", + "type": "imports", + "label": "imports status/category/source constants" + }, + { + "source": "file:src/components/applications/FilterBar.tsx", + "target": "file:src/services/api.ts", + "type": "imports", + "label": "imports getExportUrl, getSampleCsvUrl" + }, + { + "source": "file:src/components/applications/HistoryPanel.tsx", + "target": "file:src/types/application.ts", + "type": "imports", + "label": "imports HistoryEntry" + }, + { + "source": "file:src/components/applications/HistoryPanel.tsx", + "target": "file:src/queries/applicationQueries.ts", + "type": "imports", + "label": "imports useApplicationHistory" + }, + { + "source": "file:src/components/applications/HistoryPanel.tsx", + "target": "file:src/queries/applicationMutations.ts", + "type": "imports", + "label": "imports useRestoreToVersion" + }, + { + "source": "file:src/components/applications/HistoryPanel.tsx", + "target": "file:src/components/applications/FieldDiff.tsx", + "type": "imports", + "label": "imports FieldDiff" + }, + { + "source": "file:src/components/applications/ImportModal.tsx", + "target": "file:src/components/ui/Modal.tsx", + "type": "imports", + "label": "imports Modal" + }, + { + "source": "file:src/components/applications/ImportModal.tsx", + "target": "file:src/components/ui/Button.tsx", + "type": "imports", + "label": "imports Button" + }, + { + "source": "file:src/components/applications/ImportModal.tsx", + "target": "file:src/queries/applicationMutations.ts", + "type": "imports", + "label": "imports useImportApplications" + }, + { + "source": "file:src/components/applications/ImportModal.tsx", + "target": "file:src/services/api.ts", + "type": "imports", + "label": "imports getSampleCsvUrl" + }, + { + "source": "file:src/components/applications/ImportModal.tsx", + "target": "file:src/types/application.ts", + "type": "imports", + "label": "imports ImportResult" + }, + { + "source": "file:src/components/common/Header.tsx", + "target": "file:src/components/ui/Button.tsx", + "type": "imports", + "label": "imports Button" + }, + { + "source": "file:src/components/applications/index.ts", + "target": "file:src/components/applications/ApplicationCard.tsx", + "type": "imports", + "label": "re-exports" + }, + { + "source": "file:src/components/applications/index.ts", + "target": "file:src/components/applications/ApplicationEdit.tsx", + "type": "imports", + "label": "re-exports" + }, + { + "source": "file:src/components/applications/index.ts", + "target": "file:src/components/applications/ApplicationList.tsx", + "type": "imports", + "label": "re-exports" + }, + { + "source": "file:src/components/applications/index.ts", + "target": "file:src/components/applications/FilterBar.tsx", + "type": "imports", + "label": "re-exports" + }, + { + "source": "file:src/components/applications/index.ts", + "target": "file:src/components/applications/ImportModal.tsx", + "type": "imports", + "label": "re-exports" + }, + { + "source": "file:src/components/common/index.ts", + "target": "file:src/components/common/Header.tsx", + "type": "imports", + "label": "re-exports" + }, + { + "source": "file:src/components/interviews/InlineInterviewStageForm.tsx", + "target": "file:src/types/application.ts", + "type": "imports", + "label": "imports interview stage types" + }, + { + "source": "file:src/components/interviews/InlineInterviewStageForm.tsx", + "target": "file:src/components/ui/Input.tsx", + "type": "imports", + "label": "imports Input, TextArea" + }, + { + "source": "file:src/components/interviews/InlineInterviewStageForm.tsx", + "target": "file:src/components/ui/Checkbox.tsx", + "type": "imports", + "label": "imports Checkbox" + }, + { + "source": "file:src/components/interviews/InlineInterviewStageForm.tsx", + "target": "file:src/components/ui/Button.tsx", + "type": "imports", + "label": "imports Button" + }, + { + "source": "file:src/components/interviews/InlineInterviewStageForm.tsx", + "target": "file:src/components/ui/Rating.tsx", + "type": "imports", + "label": "imports RatingInput" + }, + { + "source": "file:src/components/interviews/InterviewStageForm.tsx", + "target": "file:src/types/application.ts", + "type": "imports", + "label": "imports interview stage types" + }, + { + "source": "file:src/components/interviews/InterviewStageForm.tsx", + "target": "file:src/components/ui/Modal.tsx", + "type": "imports", + "label": "imports Modal" + }, + { + "source": "file:src/components/interviews/InterviewStageForm.tsx", + "target": "file:src/components/ui/Input.tsx", + "type": "imports", + "label": "imports Input, TextArea" + }, + { + "source": "file:src/components/interviews/InterviewStageForm.tsx", + "target": "file:src/components/ui/Checkbox.tsx", + "type": "imports", + "label": "imports Checkbox" + }, + { + "source": "file:src/components/interviews/InterviewStageForm.tsx", + "target": "file:src/components/ui/Button.tsx", + "type": "imports", + "label": "imports Button" + }, + { + "source": "file:src/components/interviews/InterviewStageForm.tsx", + "target": "file:src/components/ui/Rating.tsx", + "type": "imports", + "label": "imports RatingInput" + }, + { + "source": "file:src/components/interviews/InterviewStageItem.tsx", + "target": "file:src/types/application.ts", + "type": "imports", + "label": "imports InterviewStage" + }, + { + "source": "file:src/components/interviews/InterviewStageItem.tsx", + "target": "file:src/components/ui/Button.tsx", + "type": "imports", + "label": "imports Button" + }, + { + "source": "file:src/components/interviews/InterviewStageItem.tsx", + "target": "file:src/components/ui/Rating.tsx", + "type": "imports", + "label": "imports RatingDisplay" + }, + { + "source": "file:src/components/interviews/InterviewStageItem.tsx", + "target": "file:src/components/ui/Checkbox.tsx", + "type": "imports", + "label": "imports Checkbox" + }, + { + "source": "file:src/components/interviews/InterviewStageItem.tsx", + "target": "file:src/components/ui/Modal.tsx", + "type": "imports", + "label": "imports ConfirmDialog" + }, + { + "source": "file:src/components/interviews/InterviewStageItem.tsx", + "target": "file:src/lib/utils.ts", + "type": "imports", + "label": "imports formatDate, getTodayDate, cn" + }, + { + "source": "file:src/components/interviews/InterviewStageList.tsx", + "target": "file:src/types/application.ts", + "type": "imports", + "label": "imports InterviewStage types" + }, + { + "source": "file:src/components/interviews/InterviewStageList.tsx", + "target": "file:src/components/ui/Button.tsx", + "type": "imports", + "label": "imports Button" + }, + { + "source": "file:src/components/interviews/InterviewStageList.tsx", + "target": "file:src/components/interviews/InterviewStageItem.tsx", + "type": "imports", + "label": "imports InterviewStageItem" + }, + { + "source": "file:src/components/interviews/InterviewStageList.tsx", + "target": "file:src/components/interviews/InterviewStageForm.tsx", + "type": "imports", + "label": "imports InterviewStageForm" + }, + { + "source": "file:src/components/interviews/InterviewStageList.tsx", + "target": "file:src/lib/constants.ts", + "type": "imports", + "label": "imports DEFAULT_INTERVIEW_STAGES" + }, + { + "source": "file:src/components/ui/Badge.tsx", + "target": "file:src/lib/utils.ts", + "type": "imports", + "label": "imports cn" + }, + { + "source": "file:src/components/ui/Badge.tsx", + "target": "file:src/types/application.ts", + "type": "imports", + "label": "imports ApplicationStatus" + }, + { + "source": "file:src/components/ui/Badge.tsx", + "target": "file:src/lib/constants.ts", + "type": "imports", + "label": "imports STATUS_COLORS, APPLICATION_STATUSES" + }, + { + "source": "file:src/components/ui/Button.tsx", + "target": "file:src/lib/utils.ts", + "type": "imports", + "label": "imports cn" + }, + { + "source": "file:src/components/ui/Card.tsx", + "target": "file:src/lib/utils.ts", + "type": "imports", + "label": "imports cn" + }, + { + "source": "file:src/components/ui/Input.tsx", + "target": "file:src/lib/utils.ts", + "type": "imports", + "label": "imports cn" + }, + { + "source": "file:src/components/ui/Modal.tsx", + "target": "file:src/lib/utils.ts", + "type": "imports", + "label": "imports cn" + }, + { + "source": "file:src/components/ui/Modal.tsx", + "target": "file:src/components/ui/Button.tsx", + "type": "imports", + "label": "imports Button (ConfirmDialog)" + }, + { + "source": "file:src/components/ui/Pagination.tsx", + "target": "file:src/lib/utils.ts", + "type": "imports", + "label": "imports cn" + }, + { + "source": "file:src/components/ui/Pagination.tsx", + "target": "file:src/components/ui/Button.tsx", + "type": "imports", + "label": "imports Button" + }, + { + "source": "file:src/components/ui/Select.tsx", + "target": "file:src/lib/utils.ts", + "type": "imports", + "label": "imports cn" + }, + { + "source": "file:src/components/ui/UrlFieldInput.tsx", + "target": "file:src/components/ui/Input.tsx", + "type": "imports", + "label": "imports Input" + }, + { + "source": "file:src/components/interviews/index.ts", + "target": "file:src/components/interviews/InlineInterviewStageForm.tsx", + "type": "imports", + "label": "re-exports" + }, + { + "source": "file:src/components/interviews/index.ts", + "target": "file:src/components/interviews/InterviewStageForm.tsx", + "type": "imports", + "label": "re-exports" + }, + { + "source": "file:src/components/interviews/index.ts", + "target": "file:src/components/interviews/InterviewStageItem.tsx", + "type": "imports", + "label": "re-exports" + }, + { + "source": "file:src/components/interviews/index.ts", + "target": "file:src/components/interviews/InterviewStageList.tsx", + "type": "imports", + "label": "re-exports" + }, + { + "source": "file:src/components/ui/index.ts", + "target": "file:src/components/ui/Badge.tsx", + "type": "imports", + "label": "re-exports" + }, + { + "source": "file:src/components/ui/index.ts", + "target": "file:src/components/ui/Button.tsx", + "type": "imports", + "label": "re-exports" + }, + { + "source": "file:src/components/ui/index.ts", + "target": "file:src/components/ui/Card.tsx", + "type": "imports", + "label": "re-exports" + }, + { + "source": "file:src/components/ui/index.ts", + "target": "file:src/components/ui/Checkbox.tsx", + "type": "imports", + "label": "re-exports" + }, + { + "source": "file:src/components/ui/index.ts", + "target": "file:src/components/ui/EmptyState.tsx", + "type": "imports", + "label": "re-exports" + }, + { + "source": "file:src/components/ui/index.ts", + "target": "file:src/components/ui/Input.tsx", + "type": "imports", + "label": "re-exports" + }, + { + "source": "file:src/components/ui/index.ts", + "target": "file:src/components/ui/Modal.tsx", + "type": "imports", + "label": "re-exports" + }, + { + "source": "file:src/components/ui/index.ts", + "target": "file:src/components/ui/Pagination.tsx", + "type": "imports", + "label": "re-exports" + }, + { + "source": "file:src/components/ui/index.ts", + "target": "file:src/components/ui/Rating.tsx", + "type": "imports", + "label": "re-exports" + }, + { + "source": "file:src/components/ui/index.ts", + "target": "file:src/components/ui/Select.tsx", + "type": "imports", + "label": "re-exports" + }, + { + "source": "file:src/components/ui/index.ts", + "target": "file:src/components/ui/UrlFieldInput.tsx", + "type": "imports", + "label": "re-exports" + }, + { + "source": "file:src/router.tsx", + "target": "file:vite.config.ts", + "type": "imports", + "label": "built by Vite with TanStack Start plugin" + }, + { + "source": "file:src/router.tsx", + "target": "file:tsconfig.json", + "type": "imports", + "label": "TypeScript compiler config" + }, + { + "source": "file:vite.config.ts", + "target": "file:package.json", + "type": "imports", + "label": "references package dependencies" + }, + { + "source": "file:src/routes/__root.tsx", + "target": "file:src/index.css", + "type": "imports", + "label": "imports global CSS" + } + ], + "layers": [ + { + "id": "layer:config", + "name": "Configuration", + "description": "Build tooling, TypeScript configuration, and package manifest. Vite handles both dev server (port 3040) and /api proxy to FastAPI. TanStack Start's Vite plugin enables SSR.", + "nodeIds": [ + "file:vite.config.ts", + "file:tsconfig.json", + "file:package.json" + ] + }, + { + "id": "layer:domain", + "name": "Domain Model & Constants", + "description": "Core TypeScript types, domain constants, and application-wide configuration values. The single source of truth for ApplicationStatus, CompanyCategory, JobSource, and all related interfaces.", + "nodeIds": [ + "file:src/types/application.ts", + "file:src/lib/constants.ts", + "file:src/index.css" + ] + }, + { + "id": "layer:utilities", + "name": "Utilities & Infrastructure", + "description": "Pure utility functions, query client singleton, query key factory. Shared infrastructure used across the app for formatting, cache management, and type-safe cache invalidation.", + "nodeIds": [ + "file:src/lib/utils.ts", + "file:src/lib/utils.test.ts", + "file:src/lib/queryClient.ts", + "file:src/queries/queryKeys.ts" + ] + }, + { + "id": "layer:api", + "name": "API Layer", + "description": "Client-side and server-side API communication. Client API (services/api.ts) makes browser-side fetch calls proxied through Vite to FastAPI. Server API (server/) uses TanStack Start server functions for SSR data fetching directly from the backend.", + "nodeIds": [ + "file:src/services/api.ts", + "file:src/server/api.ts", + "file:src/server/applications.ts" + ] + }, + { + "id": "layer:data", + "name": "Data Fetching & State", + "description": "TanStack Query hooks for reading and writing application data. Queries provide cache-aware data fetching. Mutations handle all write operations with automatic cache invalidation. Custom hooks encapsulate filter and sort UI state.", + "nodeIds": [ + "file:src/queries/applicationQueries.ts", + "file:src/queries/applicationMutations.ts", + "file:src/hooks/useFilters.ts", + "file:src/hooks/useSorting.ts", + "file:src/hooks/useHydrated.ts" + ] + }, + { + "id": "layer:routing", + "name": "Routing & App Shell", + "description": "TanStack Router file-based routing. Router entry, auto-generated route tree, root document layout, and individual page routes. SSR data loading via loader functions with server function calls.", + "nodeIds": [ + "file:src/router.tsx", + "file:src/routeTree.gen.ts", + "file:src/routes/__root.tsx", + "file:src/routes/index.tsx", + "file:src/routes/applications/$id.tsx", + "file:src/routes/applications/new.tsx" + ] + }, + { + "id": "layer:ui-primitives", + "name": "UI Primitives", + "description": "Reusable design system components. Atomic building blocks: Button, Input, TextArea, Select, Checkbox, Card, Badge, Modal, ConfirmDialog, Pagination, Rating, EmptyState, UrlFieldInput. All support dark mode via Tailwind.", + "nodeIds": [ + "file:src/components/ui/Badge.tsx", + "file:src/components/ui/Button.tsx", + "file:src/components/ui/Card.tsx", + "file:src/components/ui/Checkbox.tsx", + "file:src/components/ui/EmptyState.tsx", + "file:src/components/ui/Input.tsx", + "file:src/components/ui/Modal.tsx", + "file:src/components/ui/Pagination.tsx", + "file:src/components/ui/Rating.tsx", + "file:src/components/ui/Select.tsx", + "file:src/components/ui/UrlFieldInput.tsx", + "file:src/components/ui/index.ts" + ] + }, + { + "id": "layer:interview-components", + "name": "Interview Stage Components", + "description": "Components for managing the interview pipeline for an application. InterviewStageList orchestrates stage management with progress tracking. InterviewStageItem is the individual row with toggle/expand. Two form variants: InterviewStageForm (modal) and InlineInterviewStageForm (embedded).", + "nodeIds": [ + "file:src/components/interviews/InlineInterviewStageForm.tsx", + "file:src/components/interviews/InterviewStageForm.tsx", + "file:src/components/interviews/InterviewStageItem.tsx", + "file:src/components/interviews/InterviewStageList.tsx", + "file:src/components/interviews/index.ts" + ] + }, + { + "id": "layer:application-components", + "name": "Application Feature Components", + "description": "Feature-level components for the job application tracking domain. ApplicationList/Card for list view, ApplicationEdit for create/edit form, FilterBar for list controls, HistoryPanel for audit trail, FieldDiff for change display, ImportModal for CSV import.", + "nodeIds": [ + "file:src/components/applications/ApplicationCard.tsx", + "file:src/components/applications/ApplicationEdit.tsx", + "file:src/components/applications/ApplicationList.tsx", + "file:src/components/applications/FieldDiff.tsx", + "file:src/components/applications/FilterBar.tsx", + "file:src/components/applications/HistoryPanel.tsx", + "file:src/components/applications/ImportModal.tsx", + "file:src/components/applications/index.ts", + "file:src/components/common/Header.tsx", + "file:src/components/common/index.ts" + ] + } + ], + "tour": [ + { + "id": "step:entry-point", + "order": 1, + "title": "Application Entry Point", + "description": "Start with src/router.tsx \u2014 this is where the TanStack Router instance is created. It wires up the auto-generated routeTree, configures SSR dehydration/hydration of TanStack Query state (so server-fetched data is reused on the client without extra requests), and registers the router type globally. The routeTree.gen.ts file is auto-generated by TanStack Router from the files in src/routes/.", + "nodeIds": [ + "file:src/router.tsx", + "file:src/routeTree.gen.ts" + ] + }, + { + "id": "step:domain-model", + "order": 2, + "title": "Domain Model", + "description": "src/types/application.ts is the single source of truth for all domain types. It defines Application (the core entity with 20+ fields), InterviewStage, all status/category/source union types, input types for create/update operations, paginated response shapes, filter/sort state interfaces, and history/diff types. Read this file first to understand what data the app manages.", + "nodeIds": [ + "file:src/types/application.ts", + "file:src/lib/constants.ts" + ] + }, + { + "id": "step:app-shell", + "order": 3, + "title": "App Shell & Root Layout", + "description": "src/routes/__root.tsx defines the HTML document structure for SSR. It includes HeadContent and Scripts (TanStack Router's SSR primitives), wraps everything in QueryClientProvider, and renders the Header plus an Outlet for child routes. Note the inline dark mode script \u2014 this prevents flash of unstyled content (FOUC) by applying the dark class before React hydrates.", + "nodeIds": [ + "file:src/routes/__root.tsx", + "file:src/components/common/Header.tsx" + ] + }, + { + "id": "step:api-dual-layer", + "order": 4, + "title": "Dual API Architecture", + "description": "The app has two API layers: (1) src/services/api.ts \u2014 browser-side fetch to /api/* proxied through Vite to FastAPI on port 5160, used by TanStack Query mutations and queries. (2) src/server/ \u2014 TanStack Start server functions that run on the server during SSR, calling FastAPI directly via server-to-server HTTP. This dual pattern avoids browser-to-server roundtrips for initial page loads.", + "nodeIds": [ + "file:src/services/api.ts", + "file:src/server/api.ts", + "file:src/server/applications.ts" + ] + }, + { + "id": "step:query-layer", + "order": 5, + "title": "Data Fetching Layer", + "description": "src/queries/ contains the TanStack Query layer. queryKeys.ts defines a hierarchical key factory for type-safe cache invalidation. applicationQueries.ts provides read hooks (useApplications, useApplication, useApplicationHistory). applicationMutations.ts provides all write hooks \u2014 each automatically invalidates the right cache entries on success. The queryClient singleton is shared between SSR and client.", + "nodeIds": [ + "file:src/queries/queryKeys.ts", + "file:src/queries/applicationQueries.ts", + "file:src/queries/applicationMutations.ts", + "file:src/lib/queryClient.ts" + ] + }, + { + "id": "step:list-route", + "order": 6, + "title": "Home Page \u2014 Application List", + "description": "src/routes/index.tsx is the main listing page. Its loader prefetches application data via the server function, which gets hydrated into the TanStack Query cache (so the client Query renders immediately from cache). The component uses useFilters and useSorting hooks, then renders FilterBar, ApplicationList, and ImportModal. Pagination state is local (useState).", + "nodeIds": [ + "file:src/routes/index.tsx", + "file:src/hooks/useFilters.ts", + "file:src/hooks/useSorting.ts" + ] + }, + { + "id": "step:list-components", + "order": 7, + "title": "List View Components", + "description": "The list view is built from ApplicationList \u2192 ApplicationCard. ApplicationList handles loading skeletons, empty states (with and without active filters), and delegates to Pagination. ApplicationCard renders each application's summary with a context menu (three-dot) for archive/delete. FilterBar provides all filter/sort controls plus CSV import/export buttons.", + "nodeIds": [ + "file:src/components/applications/ApplicationList.tsx", + "file:src/components/applications/ApplicationCard.tsx", + "file:src/components/applications/FilterBar.tsx" + ] + }, + { + "id": "step:edit-route", + "order": 8, + "title": "Application Edit/Create", + "description": "src/routes/applications/$id.tsx and new.tsx both render ApplicationEdit. The $id route uses SSR loader + useHydrated hook to prevent hydration mismatch on the complex form. ApplicationEdit handles both create mode (no applicationId) and edit mode, manages a local FormState, tracks isDirty, and uses useBlocker to guard against accidental navigation with unsaved changes.", + "nodeIds": [ + "file:src/routes/applications/$id.tsx", + "file:src/routes/applications/new.tsx", + "file:src/components/applications/ApplicationEdit.tsx", + "file:src/hooks/useHydrated.ts" + ] + }, + { + "id": "step:interview-stages", + "order": 9, + "title": "Interview Pipeline", + "description": "The interview stage feature is in src/components/interviews/. InterviewStageList manages the pipeline with a completion progress bar and Add Default Stages button. InterviewStageItem shows each stage as an expandable row with completion toggle and performance rating. Two form variants exist: InterviewStageForm (modal for the list view) and InlineInterviewStageForm (embedded in ApplicationEdit).", + "nodeIds": [ + "file:src/components/interviews/InterviewStageList.tsx", + "file:src/components/interviews/InterviewStageItem.tsx", + "file:src/components/interviews/InterviewStageForm.tsx", + "file:src/components/interviews/InlineInterviewStageForm.tsx" + ] + }, + { + "id": "step:history", + "order": 10, + "title": "Change History & Audit Trail", + "description": "HistoryPanel is a fixed right-side drawer that fetches application history entries and displays them in reverse-chronological order. Each entry can be expanded to reveal FieldDiff, which shows old value (red strikethrough) \u2192 new value (green) for every changed field. Non-current entries have a Restore button that calls useRestoreToVersion to roll back the application state.", + "nodeIds": [ + "file:src/components/applications/HistoryPanel.tsx", + "file:src/components/applications/FieldDiff.tsx" + ] + }, + { + "id": "step:csv-import", + "order": 11, + "title": "CSV Import/Export", + "description": "ImportModal provides a file upload UI for importing applications from CSV. It uses the useImportApplications mutation which POSTs the file as FormData to /api/applications/import. After import, it shows a results summary (imported/skipped/error counts). FilterBar also has direct links to the export and sample CSV template endpoints.", + "nodeIds": [ + "file:src/components/applications/ImportModal.tsx" + ] + }, + { + "id": "step:ui-system", + "order": 12, + "title": "UI Design System", + "description": "src/components/ui/ contains all primitive components: Button (4 variants), Input/TextArea, Select, Checkbox, Card, Badge (status-colored), Modal/ConfirmDialog (portal-based), Pagination (windowed), Rating (interactive + display), EmptyState, UrlFieldInput. All support dark mode via Tailwind .dark class. cn() from utils.ts is the className merger used throughout.", + "nodeIds": [ + "file:src/components/ui/Button.tsx", + "file:src/components/ui/Modal.tsx", + "file:src/components/ui/Badge.tsx", + "file:src/components/ui/Rating.tsx", + "file:src/lib/utils.ts" + ] + } + ] +} \ No newline at end of file diff --git a/tanstack-start-ui/.understand-anything/meta.json b/tanstack-start-ui/.understand-anything/meta.json new file mode 100644 index 00000000..b8682ac6 --- /dev/null +++ b/tanstack-start-ui/.understand-anything/meta.json @@ -0,0 +1,29 @@ +{ + "projectName": "tanstack-start-ui", + "description": "TanStack Start (React SSR) UI for a job application tracker. Uses TanStack Router with file-based routing, TanStack Query v5 for server state, Tailwind CSS 4.x for styling. Pairs with FastAPI backend on port 5160.", + "gitHash": "e5054096efec8165e7d4cfe11fc103f77ad2d98f", + "generatedAt": "2026-05-04T00:00:00Z", + "pluginVersion": "2.5.1", + "languages": [ + "TypeScript", + "CSS" + ], + "frameworks": [ + "React 19", + "TanStack Start", + "TanStack Router", + "TanStack Query v5", + "Tailwind CSS 4.x", + "Vite 7" + ], + "entryPoints": [ + "src/router.tsx" + ], + "stats": { + "totalFiles": 51, + "totalNodes": 51, + "totalEdges": 137, + "totalLayers": 9, + "tourSteps": 12 + } +} diff --git a/tanstack-start-ui/package-lock.json b/tanstack-start-ui/package-lock.json index f10ba272..f848e626 100644 --- a/tanstack-start-ui/package-lock.json +++ b/tanstack-start-ui/package-lock.json @@ -80,9 +80,9 @@ } }, "node_modules/@asamuzakjp/css-color/node_modules/lru-cache": { - "version": "11.3.2", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.3.2.tgz", - "integrity": "sha512-wgWa6FWQ3QRRJbIjbsldRJZxdxYngT/dO0I5Ynmlnin8qy7tC6xYzbcJjtN4wHLXtkbVwHzk0C+OejVw1XM+DQ==", + "version": "11.3.6", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.3.6.tgz", + "integrity": "sha512-Gf/KoL3C/MlI7Bt0PGI9I+TeTC/I6r/csU58N4BSNc4lppLBeKsOdFYkK+dX0ABDUMJNfCHTyPpzwwO21Awd3A==", "dev": true, "license": "BlueOak-1.0.0", "engines": { @@ -104,9 +104,9 @@ } }, "node_modules/@asamuzakjp/dom-selector/node_modules/lru-cache": { - "version": "11.3.2", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.3.2.tgz", - "integrity": "sha512-wgWa6FWQ3QRRJbIjbsldRJZxdxYngT/dO0I5Ynmlnin8qy7tC6xYzbcJjtN4wHLXtkbVwHzk0C+OejVw1XM+DQ==", + "version": "11.3.6", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.3.6.tgz", + "integrity": "sha512-Gf/KoL3C/MlI7Bt0PGI9I+TeTC/I6r/csU58N4BSNc4lppLBeKsOdFYkK+dX0ABDUMJNfCHTyPpzwwO21Awd3A==", "dev": true, "license": "BlueOak-1.0.0", "engines": { @@ -135,9 +135,9 @@ } }, "node_modules/@babel/compat-data": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.0.tgz", - "integrity": "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==", + "version": "7.29.3", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.3.tgz", + "integrity": "sha512-LIVqM46zQWZhj17qA8wb4nW/ixr2y1Nw+r1etiAWgRM6U1IqP+LNhL1yg440jYZR72jCWcWbLWzIosH+uP1fqg==", "license": "MIT", "engines": { "node": ">=6.9.0" @@ -294,9 +294,9 @@ } }, "node_modules/@babel/parser": { - "version": "7.29.2", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.2.tgz", - "integrity": "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==", + "version": "7.29.3", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.3.tgz", + "integrity": "sha512-b3ctpQwp+PROvU/cttc4OYl4MzfJUWy6FZg+PMXfzmt/+39iHVF0sDfqay8TQM3JA2EUOyKcFZt75jWriQijsA==", "license": "MIT", "dependencies": { "@babel/types": "^7.29.0" @@ -446,9 +446,9 @@ } }, "node_modules/@csstools/css-calc": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-3.1.1.tgz", - "integrity": "sha512-HJ26Z/vmsZQqs/o3a6bgKslXGFAungXGbinULZO3eMsOyNJHeBBZfup5FiZInOghgoM4Hwnmw+OgbJCNg1wwUQ==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-3.2.0.tgz", + "integrity": "sha512-bR9e6o2BDB12jzN/gIbjHa5wLJ4UjD1CB9pM7ehlc0ddk6EBz+yYS1EV2MF55/HUxrHcB/hehAyt5vhsA3hx7w==", "dev": true, "funding": [ { @@ -470,9 +470,9 @@ } }, "node_modules/@csstools/css-color-parser": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-4.0.2.tgz", - "integrity": "sha512-0GEfbBLmTFf0dJlpsNU7zwxRIH0/BGEMuXLTCvFYxuL1tNhqzTbtnFICyJLTNK4a+RechKP75e7w42ClXSnJQw==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-4.1.0.tgz", + "integrity": "sha512-U0KhLYmy2GVj6q4T3WaAe6NPuFYCPQoE3b0dRGxejWDgcPp8TP7S5rVdM5ZrFaqu4N67X8YaPBw14dQSYx3IyQ==", "dev": true, "funding": [ { @@ -487,7 +487,7 @@ "license": "MIT", "dependencies": { "@csstools/color-helpers": "^6.0.2", - "@csstools/css-calc": "^3.1.1" + "@csstools/css-calc": "^3.2.0" }, "engines": { "node": ">=20.19.0" @@ -521,9 +521,9 @@ } }, "node_modules/@csstools/css-syntax-patches-for-csstree": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.1.2.tgz", - "integrity": "sha512-5GkLzz4prTIpoyeUiIu3iV6CSG3Plo7xRVOFPKI7FVEJ3mZ0A8SwK0XU3Gl7xAkiQ+mDyam+NNp875/C5y+jSA==", + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.1.3.tgz", + "integrity": "sha512-SH60bMfrRCJF3morcdk57WklujF4Jr/EsQUzqkarfHXEFcAR1gg7fS/chAE922Sehgzc1/+Tz5H3Ypa1HiEKrg==", "dev": true, "funding": [ { @@ -1011,13 +1011,13 @@ } }, "node_modules/@eslint/config-array": { - "version": "0.23.4", - "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.23.4.tgz", - "integrity": "sha512-lf19F24LSMfF8weXvW5QEtnLqW70u7kgit5e9PSx0MsHAFclGd1T9ynvWEMDT1w5J4Qt54tomGeAhdoAku1Xow==", + "version": "0.23.5", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.23.5.tgz", + "integrity": "sha512-Y3kKLvC1dvTOT+oGlqNQ1XLqK6D1HU2YXPc52NmAlJZbMMWDzGYXMiPRJ8TYD39muD/OTjlZmNJ4ib7dvSrMBA==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@eslint/object-schema": "^3.0.4", + "@eslint/object-schema": "^3.0.5", "debug": "^4.3.1", "minimatch": "^10.2.4" }, @@ -1026,22 +1026,22 @@ } }, "node_modules/@eslint/config-helpers": { - "version": "0.5.4", - "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.5.4.tgz", - "integrity": "sha512-jJhqiY3wPMlWWO3370M86CPJ7pt8GmEwSLglMfQhjXal07RCvhmU0as4IuUEW5SJeunfItiEetHmSxCCe9lDBg==", + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.5.5.tgz", + "integrity": "sha512-eIJYKTCECbP/nsKaaruF6LW967mtbQbsw4JTtSVkUQc9MneSkbrgPJAbKl9nWr0ZeowV8BfsarBmPpBzGelA2w==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@eslint/core": "^1.2.0" + "@eslint/core": "^1.2.1" }, "engines": { "node": "^20.19.0 || ^22.13.0 || >=24" } }, "node_modules/@eslint/core": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-1.2.0.tgz", - "integrity": "sha512-8FTGbNzTvmSlc4cZBaShkC6YvFMG0riksYWRFKXztqVdXaQbcZLXlFbSpC05s70sGEsXAw0qwhx69JiW7hQS7A==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-1.2.1.tgz", + "integrity": "sha512-MwcE1P+AZ4C6DWlpin/OmOA54mmIZ/+xZuJiQd4SyB29oAJjN30UW9wkKNptW2ctp4cEsvhlLY/CsQ1uoHDloQ==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -1073,9 +1073,9 @@ } }, "node_modules/@eslint/object-schema": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-3.0.4.tgz", - "integrity": "sha512-55lO/7+Yp0ISKRP0PsPtNTeNGapXaO085aELZmWCVc5SH3jfrqpuU6YgOdIxMS99ZHkQN1cXKE+cdIqwww9ptw==", + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-3.0.5.tgz", + "integrity": "sha512-vqTaUEgxzm+YDSdElad6PiRoX4t8VGDjCtt05zn4nU810UIx/uNEV7/lZJ6KwFThKZOzOxzXy48da+No7HZaMw==", "dev": true, "license": "Apache-2.0", "engines": { @@ -1115,29 +1115,43 @@ } }, "node_modules/@humanfs/core": { - "version": "0.19.1", - "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", - "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.2.tgz", + "integrity": "sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==", "dev": true, "license": "Apache-2.0", + "dependencies": { + "@humanfs/types": "^0.15.0" + }, "engines": { "node": ">=18.18.0" } }, "node_modules/@humanfs/node": { - "version": "0.16.7", - "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz", - "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==", + "version": "0.16.8", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.8.tgz", + "integrity": "sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@humanfs/core": "^0.19.1", + "@humanfs/core": "^0.19.2", + "@humanfs/types": "^0.15.0", "@humanwhocodes/retry": "^0.4.0" }, "engines": { "node": ">=18.18.0" } }, + "node_modules/@humanfs/types": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/@humanfs/types/-/types-0.15.0.tgz", + "integrity": "sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, "node_modules/@humanwhocodes/module-importer": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", @@ -1266,9 +1280,9 @@ "license": "MIT" }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.60.1.tgz", - "integrity": "sha512-d6FinEBLdIiK+1uACUttJKfgZREXrF0Qc2SmLII7W2AD8FfiZ9Wjd+rD/iRuf5s5dWrr1GgwXCvPqOuDquOowA==", + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.60.3.tgz", + "integrity": "sha512-x35CNW/ANXG3hE/EZpRU8MXX1JDN86hBb2wMGAtltkz7pc6cxgjpy1OMMfDosOQ+2hWqIkag/fGok1Yady9nGw==", "cpu": [ "arm" ], @@ -1279,9 +1293,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.60.1.tgz", - "integrity": "sha512-YjG/EwIDvvYI1YvYbHvDz/BYHtkY4ygUIXHnTdLhG+hKIQFBiosfWiACWortsKPKU/+dUwQQCKQM3qrDe8c9BA==", + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.60.3.tgz", + "integrity": "sha512-xw3xtkDApIOGayehp2+Rz4zimfkaX65r4t47iy+ymQB2G4iJCBBfj0ogVg5jpvjpn8UWn/+q9tprxleYeNp3Hw==", "cpu": [ "arm64" ], @@ -1292,9 +1306,9 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.60.1.tgz", - "integrity": "sha512-mjCpF7GmkRtSJwon+Rq1N8+pI+8l7w5g9Z3vWj4T7abguC4Czwi3Yu/pFaLvA3TTeMVjnu3ctigusqWUfjZzvw==", + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.60.3.tgz", + "integrity": "sha512-vo6Y5Qfpx7/5EaamIwi0WqW2+zfiusVihKatLvtN1VFVy3D13uERk/6gZLU1UiHRL6fDXqj/ELIeVRGnvcTE1g==", "cpu": [ "arm64" ], @@ -1305,9 +1319,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.60.1.tgz", - "integrity": "sha512-haZ7hJ1JT4e9hqkoT9R/19XW2QKqjfJVv+i5AGg57S+nLk9lQnJ1F/eZloRO3o9Scy9CM3wQ9l+dkXtcBgN5Ew==", + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.60.3.tgz", + "integrity": "sha512-D+0QGcZhBzTN82weOnsSlY7V7+RMmPuF1CkbxyMAGE8+ZHeUjyb76ZiWmBlCu//AQQONvxcqRbwZTajZKqjuOw==", "cpu": [ "x64" ], @@ -1318,9 +1332,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.60.1.tgz", - "integrity": "sha512-czw90wpQq3ZsAVBlinZjAYTKduOjTywlG7fEeWKUA7oCmpA8xdTkxZZlwNJKWqILlq0wehoZcJYfBvOyhPTQ6w==", + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.60.3.tgz", + "integrity": "sha512-6HnvHCT7fDyj6R0Ph7A6x8dQS/S38MClRWeDLqc0MdfWkxjiu1HSDYrdPhqSILzjTIC/pnXbbJbo+ft+gy/9hQ==", "cpu": [ "arm64" ], @@ -1331,9 +1345,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.60.1.tgz", - "integrity": "sha512-KVB2rqsxTHuBtfOeySEyzEOB7ltlB/ux38iu2rBQzkjbwRVlkhAGIEDiiYnO2kFOkJp+Z7pUXKyrRRFuFUKt+g==", + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.60.3.tgz", + "integrity": "sha512-KHLgC3WKlUYW3ShFKnnosZDOJ0xjg9zp7au3sIm2bs/tGBeC2ipmvRh/N7JKi0t9Ue20C0dpEshi8WUubg+cnA==", "cpu": [ "x64" ], @@ -1344,12 +1358,15 @@ ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.60.1.tgz", - "integrity": "sha512-L+34Qqil+v5uC0zEubW7uByo78WOCIrBvci69E7sFASRl0X7b/MB6Cqd1lky/CtcSVTydWa2WZwFuWexjS5o6g==", + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.60.3.tgz", + "integrity": "sha512-DV6fJoxEYWJOvaZIsok7KrYl0tPvga5OZ2yvKHNNYyk/2roMLqQAbGhr78EQ5YhHpnhLKJD3S1WFusAkmUuV5g==", "cpu": [ "arm" ], + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -1357,12 +1374,15 @@ ] }, "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.60.1.tgz", - "integrity": "sha512-n83O8rt4v34hgFzlkb1ycniJh7IR5RCIqt6mz1VRJD6pmhRi0CXdmfnLu9dIUS6buzh60IvACM842Ffb3xd6Gg==", + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.60.3.tgz", + "integrity": "sha512-mQKoJAzvuOs6F+TZybQO4GOTSMUu7v0WdxEk24krQ/uUxXoPTtHjuaUuPmFhtBcM4K0ons8nrE3JyhTuCFtT/w==", "cpu": [ "arm" ], + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -1370,12 +1390,15 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.60.1.tgz", - "integrity": "sha512-Nql7sTeAzhTAja3QXeAI48+/+GjBJ+QmAH13snn0AJSNL50JsDqotyudHyMbO2RbJkskbMbFJfIJKWA6R1LCJQ==", + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.60.3.tgz", + "integrity": "sha512-Whjj2qoiJ6+OOJMGptTYazaJvjOJm+iKHpXQM1P3LzGjt7Ff++Tp7nH4N8J/BUA7R9IHfDyx4DJIflifwnbmIA==", "cpu": [ "arm64" ], + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -1383,12 +1406,15 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.60.1.tgz", - "integrity": "sha512-+pUymDhd0ys9GcKZPPWlFiZ67sTWV5UU6zOJat02M1+PiuSGDziyRuI/pPue3hoUwm2uGfxdL+trT6Z9rxnlMA==", + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.60.3.tgz", + "integrity": "sha512-4YTNHKqGng5+yiZt3mg77nmyuCfmNfX4fPmyUapBcIk+BdwSwmCWGXOUxhXbBEkFHtoN5boLj/5NON+u5QC9tg==", "cpu": [ "arm64" ], + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -1396,12 +1422,15 @@ ] }, "node_modules/@rollup/rollup-linux-loong64-gnu": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.60.1.tgz", - "integrity": "sha512-VSvgvQeIcsEvY4bKDHEDWcpW4Yw7BtlKG1GUT4FzBUlEKQK0rWHYBqQt6Fm2taXS+1bXvJT6kICu5ZwqKCnvlQ==", + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.60.3.tgz", + "integrity": "sha512-SU3kNlhkpI4UqlUc2VXPGK9o886ZsSeGfMAX2ba2b8DKmMXq4AL7KUrkSWVbb7koVqx41Yczx6dx5PNargIrEA==", "cpu": [ "loong64" ], + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -1409,12 +1438,15 @@ ] }, "node_modules/@rollup/rollup-linux-loong64-musl": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.60.1.tgz", - "integrity": "sha512-4LqhUomJqwe641gsPp6xLfhqWMbQV04KtPp7/dIp0nzPxAkNY1AbwL5W0MQpcalLYk07vaW9Kp1PBhdpZYYcEw==", + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.60.3.tgz", + "integrity": "sha512-6lDLl5h4TXpB1mTf2rQWnAk/LcXrx9vBfu/DT5TIPhvMhRWaZ5MxkIc8u4lJAmBo6klTe1ywXIUHFjylW505sg==", "cpu": [ "loong64" ], + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -1422,12 +1454,15 @@ ] }, "node_modules/@rollup/rollup-linux-ppc64-gnu": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.60.1.tgz", - "integrity": "sha512-tLQQ9aPvkBxOc/EUT6j3pyeMD6Hb8QF2BTBnCQWP/uu1lhc9AIrIjKnLYMEroIz/JvtGYgI9dF3AxHZNaEH0rw==", + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.60.3.tgz", + "integrity": "sha512-BMo8bOw8evlup/8G+cj5xWtPyp93xPdyoSN16Zy90Q2QZ0ZYRhCt6ZJSwbrRzG9HApFabjwj2p25TUPDWrhzqQ==", "cpu": [ "ppc64" ], + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -1435,12 +1470,15 @@ ] }, "node_modules/@rollup/rollup-linux-ppc64-musl": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.60.1.tgz", - "integrity": "sha512-RMxFhJwc9fSXP6PqmAz4cbv3kAyvD1etJFjTx4ONqFP9DkTkXsAMU4v3Vyc5BgzC+anz7nS/9tp4obsKfqkDHg==", + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.60.3.tgz", + "integrity": "sha512-E0L8X1dZN1/Rph+5VPF6Xj2G7JJvMACVXtamTJIDrVI44Y3K+G8gQaMEAavbqCGTa16InptiVrX6eM6pmJ+7qA==", "cpu": [ "ppc64" ], + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -1448,12 +1486,15 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.60.1.tgz", - "integrity": "sha512-QKgFl+Yc1eEk6MmOBfRHYF6lTxiiiV3/z/BRrbSiW2I7AFTXoBFvdMEyglohPj//2mZS4hDOqeB0H1ACh3sBbg==", + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.60.3.tgz", + "integrity": "sha512-oZJ/WHaVfHUiRAtmTAeo3DcevNsVvH8mbvodjZy7D5QKvCefO371SiKRpxoDcCxB3PTRTLayWBkvmDQKTcX/sw==", "cpu": [ "riscv64" ], + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -1461,12 +1502,15 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.60.1.tgz", - "integrity": "sha512-RAjXjP/8c6ZtzatZcA1RaQr6O1TRhzC+adn8YZDnChliZHviqIjmvFwHcxi4JKPSDAt6Uhf/7vqcBzQJy0PDJg==", + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.60.3.tgz", + "integrity": "sha512-Dhbyh7j9FybM3YaTgaHmVALwA8AkUwTPccyCQ79TG9AJUsMQqgN1DDEZNr4+QUfwiWvLDumW5vdwzoeUF+TNxQ==", "cpu": [ "riscv64" ], + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -1474,12 +1518,15 @@ ] }, "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.60.1.tgz", - "integrity": "sha512-wcuocpaOlaL1COBYiA89O6yfjlp3RwKDeTIA0hM7OpmhR1Bjo9j31G1uQVpDlTvwxGn2nQs65fBFL5UFd76FcQ==", + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.60.3.tgz", + "integrity": "sha512-cJd1X5XhHHlltkaypz1UcWLA8AcoIi1aWhsvaWDskD1oz2eKCypnqvTQ8ykMNI0RSmm7NkTdSqSSD7zM0xa6Ig==", "cpu": [ "s390x" ], + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -1487,12 +1534,15 @@ ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.60.1.tgz", - "integrity": "sha512-77PpsFQUCOiZR9+LQEFg9GClyfkNXj1MP6wRnzYs0EeWbPcHs02AXu4xuUbM1zhwn3wqaizle3AEYg5aeoohhg==", + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.60.3.tgz", + "integrity": "sha512-DAZDBHQfG2oQuhY7mc6I3/qB4LU2fQCjRvxbDwd/Jdvb9fypP4IJ4qmtu6lNjes6B531AI8cg1aKC2di97bUxA==", "cpu": [ "x64" ], + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -1500,12 +1550,15 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.60.1.tgz", - "integrity": "sha512-5cIATbk5vynAjqqmyBjlciMJl1+R/CwX9oLk/EyiFXDWd95KpHdrOJT//rnUl4cUcskrd0jCCw3wpZnhIHdD9w==", + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.60.3.tgz", + "integrity": "sha512-cRxsE8c13mZOh3vP+wLDxpQBRrOHDIGOWyDL93Sy0Ga8y515fBcC2pjUfFwUe5T7tqvTvWbCpg1URM/AXdWIXA==", "cpu": [ "x64" ], + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -1513,9 +1566,9 @@ ] }, "node_modules/@rollup/rollup-openbsd-x64": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.60.1.tgz", - "integrity": "sha512-cl0w09WsCi17mcmWqqglez9Gk8isgeWvoUZ3WiJFYSR3zjBQc2J5/ihSjpl+VLjPqjQ/1hJRcqBfLjssREQILw==", + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.60.3.tgz", + "integrity": "sha512-QaWcIgRxqEdQdhJqW4DJctsH6HCmo5vHxY0krHSX4jMtOqfzC+dqDGuHM87bu4H8JBeibWx7jFz+h6/4C8wA5Q==", "cpu": [ "x64" ], @@ -1526,9 +1579,9 @@ ] }, "node_modules/@rollup/rollup-openharmony-arm64": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.60.1.tgz", - "integrity": "sha512-4Cv23ZrONRbNtbZa37mLSueXUCtN7MXccChtKpUnQNgF010rjrjfHx3QxkS2PI7LqGT5xXyYs1a7LbzAwT0iCA==", + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.60.3.tgz", + "integrity": "sha512-AaXwSvUi3QIPtroAUw1t5yHGIyqKEXwH54WUocFolZhpGDruJcs8c+xPNDRn4XiQsS7MEwnYsHW2l0MBLDMkWg==", "cpu": [ "arm64" ], @@ -1539,9 +1592,9 @@ ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.60.1.tgz", - "integrity": "sha512-i1okWYkA4FJICtr7KpYzFpRTHgy5jdDbZiWfvny21iIKky5YExiDXP+zbXzm3dUcFpkEeYNHgQ5fuG236JPq0g==", + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.60.3.tgz", + "integrity": "sha512-65LAKM/bAWDqKNEelHlcHvm2V+Vfb8C6INFxQXRHCvaVN1rJfwr4NvdP4FyzUaLqWfaCGaadf6UbTm8xJeYfEg==", "cpu": [ "arm64" ], @@ -1552,9 +1605,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.60.1.tgz", - "integrity": "sha512-u09m3CuwLzShA0EYKMNiFgcjjzwqtUMLmuCJLeZWjjOYA3IT2Di09KaxGBTP9xVztWyIWjVdsB2E9goMjZvTQg==", + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.60.3.tgz", + "integrity": "sha512-EEM2gyhBF5MFnI6vMKdX1LAosE627RGBzIoGMdLloPZkXrUN0Ckqgr2Qi8+J3zip/8NVVro3/FjB+tjhZUgUHA==", "cpu": [ "ia32" ], @@ -1565,9 +1618,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-gnu": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.60.1.tgz", - "integrity": "sha512-k+600V9Zl1CM7eZxJgMyTUzmrmhB/0XZnF4pRypKAlAgxmedUA+1v9R+XOFv56W4SlHEzfeMtzujLJD22Uz5zg==", + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.60.3.tgz", + "integrity": "sha512-E5Eb5H/DpxaoXH++Qkv28RcUJboMopmdDUALBczvHMf7hNIxaDZqwY5lK12UK1BHacSmvupoEWGu+n993Z0y1A==", "cpu": [ "x64" ], @@ -1578,9 +1631,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.60.1.tgz", - "integrity": "sha512-lWMnixq/QzxyhTV6NjQJ4SFo1J6PvOX8vUx5Wb4bBPsEb+8xZ89Bz6kOXpfXj9ak9AHTQVQzlgzBEc1SyM27xQ==", + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.60.3.tgz", + "integrity": "sha512-hPt/bgL5cE+Qp+/TPHBqptcAgPzgj46mPcg/16zNUmbQk0j+mOEQV/+Lqu8QRtDV3Ek95Q6FeFITpuhl6OTsAA==", "cpu": [ "x64" ], @@ -1730,6 +1783,9 @@ "arm64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -1747,6 +1803,9 @@ "arm64" ], "dev": true, + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -1764,6 +1823,9 @@ "x64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -1781,6 +1843,9 @@ "x64" ], "dev": true, + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -2404,9 +2469,9 @@ } }, "node_modules/@tanstack/router-utils": { - "version": "1.161.6", - "resolved": "https://registry.npmjs.org/@tanstack/router-utils/-/router-utils-1.161.6.tgz", - "integrity": "sha512-nRcYw+w2OEgK6VfjirYvGyPLOK+tZQz1jkYcmH5AjMamQ9PycnlxZF2aEZtPpNoUsaceX2bHptn6Ub5hGXqNvw==", + "version": "1.161.8", + "resolved": "https://registry.npmjs.org/@tanstack/router-utils/-/router-utils-1.161.8.tgz", + "integrity": "sha512-xyiLWEKjfBAVhauDSSjXxyf7s8elU6SM+V050sbkofvGmIIvkwPFtDsX7Gvwh14kBd6iCwAT+RiPvXTxAptY0Q==", "license": "MIT", "dependencies": { "@babel/core": "^7.28.5", @@ -2549,6 +2614,71 @@ "node": ">=6.9.0" } }, + "node_modules/@tanstack/start-plugin-core/node_modules/@tanstack/history": { + "version": "1.161.6", + "resolved": "https://registry.npmjs.org/@tanstack/history/-/history-1.161.6.tgz", + "integrity": "sha512-NaOGLRrddszbQj9upGat6HG/4TKvXLvu+osAIgfxPYA+eIvYKv8GKDJOrY2D3/U9MRnKfMWD7bU4jeD4xmqyIg==", + "extraneous": true, + "license": "MIT", + "engines": { + "node": ">=20.19" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/start-plugin-core/node_modules/@tanstack/react-router": { + "version": "1.169.2", + "resolved": "https://registry.npmjs.org/@tanstack/react-router/-/react-router-1.169.2.tgz", + "integrity": "sha512-OJM7Kguc7ERnweaNRWsyWgIKcl3z23rD1B4jaxjzd9RGdnzpt2HfrWa9rggbT0Hfzhfo4D2ZmsfoTme035tniQ==", + "extraneous": true, + "license": "MIT", + "dependencies": { + "@tanstack/history": "1.161.6", + "@tanstack/react-store": "^0.9.3", + "@tanstack/router-core": "1.169.2", + "isbot": "^5.1.22" + }, + "engines": { + "node": ">=20.19" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": ">=18.0.0 || >=19.0.0", + "react-dom": ">=18.0.0 || >=19.0.0" + } + }, + "node_modules/@tanstack/start-plugin-core/node_modules/@tanstack/react-router/node_modules/@tanstack/router-core": { + "version": "1.169.2", + "resolved": "https://registry.npmjs.org/@tanstack/router-core/-/router-core-1.169.2.tgz", + "integrity": "sha512-5sm0DJF1A7Mz+9gy4Gz/lLovNailK3yot4vYvz9MkBUPw26uLnhQiR8hSCYxucjE0wD6Mdlc5l+Z0/XTlZ7xHw==", + "extraneous": true, + "license": "MIT", + "dependencies": { + "@tanstack/history": "1.161.6", + "cookie-es": "^3.0.0", + "seroval": "^1.5.4", + "seroval-plugins": "^1.5.4" + }, + "engines": { + "node": ">=20.19" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/start-plugin-core/node_modules/@tanstack/react-router/node_modules/cookie-es": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/cookie-es/-/cookie-es-3.1.1.tgz", + "integrity": "sha512-UaXxwISYJPTr9hwQxMFYZ7kNhSXboMXP+Z3TRX6f1/NyaGPfuNUZOWP1pUEb75B2HjfklIYLVRfWiFZJyC6Npg==", + "extraneous": true, + "license": "MIT" + }, "node_modules/@tanstack/start-plugin-core/node_modules/@tanstack/router-core": { "version": "1.167.1", "resolved": "https://registry.npmjs.org/@tanstack/router-core/-/router-core-1.167.1.tgz", @@ -3396,9 +3526,9 @@ } }, "node_modules/ajv": { - "version": "6.14.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", - "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", + "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", "dev": true, "license": "MIT", "dependencies": { @@ -3532,9 +3662,9 @@ } }, "node_modules/baseline-browser-mapping": { - "version": "2.10.16", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.16.tgz", - "integrity": "sha512-Lyf3aK28zpsD1yQMiiHD4RvVb6UdMoo8xzG2XzFIfR9luPzOpcBlAsT/qfB1XWS1bxWT+UtE4WmQgsp297FYOA==", + "version": "2.10.27", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.27.tgz", + "integrity": "sha512-zEs/ufmZoUd7WftKpKyXaT6RFxpQ5Qm9xytKRHvJfxFV9DFJkZph9RvJ1LcOUi0Z1ZVijMte65JbILeV+8QQEA==", "license": "Apache-2.0", "bin": { "baseline-browser-mapping": "dist/cli.cjs" @@ -3630,9 +3760,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001786", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001786.tgz", - "integrity": "sha512-4oxTZEvqmLLrERwxO76yfKM7acZo310U+v4kqexI2TL1DkkUEMT8UijrxxcnVdxR3qkVf5awGRX+4Z6aPHVKrA==", + "version": "1.0.30001792", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001792.tgz", + "integrity": "sha512-hVLMUZFgR4JJ6ACt1uEESvQN1/dBVqPAKY0hgrV70eN3391K6juAfTjKZLKvOMsx8PxA7gsY1/tLMMTcfFLLpw==", "funding": [ { "type": "opencollective", @@ -3830,9 +3960,9 @@ } }, "node_modules/cssstyle/node_modules/lru-cache": { - "version": "11.3.2", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.3.2.tgz", - "integrity": "sha512-wgWa6FWQ3QRRJbIjbsldRJZxdxYngT/dO0I5Ynmlnin8qy7tC6xYzbcJjtN4wHLXtkbVwHzk0C+OejVw1XM+DQ==", + "version": "11.3.6", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.3.6.tgz", + "integrity": "sha512-Gf/KoL3C/MlI7Bt0PGI9I+TeTC/I6r/csU58N4BSNc4lppLBeKsOdFYkK+dX0ABDUMJNfCHTyPpzwwO21Awd3A==", "dev": true, "license": "BlueOak-1.0.0", "engines": { @@ -3994,9 +4124,9 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.5.332", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.332.tgz", - "integrity": "sha512-7OOtytmh/rINMLwaFTbcMVvYXO3AUm029X0LcyfYk0B557RlPkdpTpnH9+htMlfu5dKwOmT0+Zs2Aw+lnn6TeQ==", + "version": "1.5.351", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.351.tgz", + "integrity": "sha512-9D7Iqx8RImSvCnOsj86rCH6eQjZFQoM04Jn6HnZVM0Nu/G58/gmKYQ1d12MZTbjQbQSTGI8nwEy07ErsA2slLA==", "license": "ISC" }, "node_modules/encoding-sniffer": { @@ -4013,14 +4143,14 @@ } }, "node_modules/enhanced-resolve": { - "version": "5.20.1", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.20.1.tgz", - "integrity": "sha512-Qohcme7V1inbAfvjItgw0EaxVX5q2rdVEZHRBrEQdRZTssLDGsL8Lwrznl8oQ/6kuTJONLaDcGjkNP247XEhcA==", + "version": "5.21.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.21.0.tgz", + "integrity": "sha512-otxSQPw4lkOZWkHpB3zaEQs6gWYEsmX4xQF68ElXC/TWvGxGMSGOvoNbaLXm6/cS/fSfHtsEdw90y20PCd+sCA==", "dev": true, "license": "MIT", "dependencies": { "graceful-fs": "^4.2.4", - "tapable": "^2.3.0" + "tapable": "^2.3.3" }, "engines": { "node": ">=10.13.0" @@ -4490,9 +4620,9 @@ } }, "node_modules/get-tsconfig": { - "version": "4.13.7", - "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.7.tgz", - "integrity": "sha512-7tN6rFgBlMgpBML5j8typ92BKFi2sFQvIdpAqLA2beia5avZDrMs0FLZiM5etShWq5irVyGcGMEA1jcDaK7A/Q==", + "version": "4.14.0", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.14.0.tgz", + "integrity": "sha512-yTb+8DXzDREzgvYmh6s9vHsSVCHeC0G3PI5bEXNBHtmshPnO+S5O7qgLEOn0I5QvMy6kpZN8K1NKGyilLb93wA==", "license": "MIT", "dependencies": { "resolve-pkg-maps": "^1.0.0" @@ -4747,9 +4877,9 @@ "license": "MIT" }, "node_modules/isbot": { - "version": "5.1.37", - "resolved": "https://registry.npmjs.org/isbot/-/isbot-5.1.37.tgz", - "integrity": "sha512-5bcicX81xf6NlTEV8rWdg7Pk01LFizDetuYGHx6d/f6y3lR2/oo8IfxjzJqn1UdDEyCcwT9e7NRloj8DwCYujQ==", + "version": "5.1.40", + "resolved": "https://registry.npmjs.org/isbot/-/isbot-5.1.40.tgz", + "integrity": "sha512-yNeeynhhtIVRBk12tBV4eHNxwB42HzR4Q3Ea7vCOiJhImGaAIdIMrbJtacQlBizGLjUPw+akkFI5Dn9T70XoVQ==", "license": "Unlicense", "engines": { "node": ">=18" @@ -4763,9 +4893,9 @@ "license": "ISC" }, "node_modules/jiti": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz", - "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.7.0.tgz", + "integrity": "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==", "devOptional": true, "license": "MIT", "bin": { @@ -4831,26 +4961,26 @@ } }, "node_modules/jsdom/node_modules/entities": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", - "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-8.0.0.tgz", + "integrity": "sha512-zwfzJecQ/Uej6tusMqwAqU/6KL2XaB2VZ2Jg54Je6ahNBGNH6Ek6g3jjNCF0fG9EWQKGZNddNjU5F1ZQn/sBnA==", "dev": true, "license": "BSD-2-Clause", "engines": { - "node": ">=0.12" + "node": ">=20.19.0" }, "funding": { "url": "https://github.com/fb55/entities?sponsor=1" } }, "node_modules/jsdom/node_modules/parse5": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-8.0.0.tgz", - "integrity": "sha512-9m4m5GSgXjL4AjumKzq1Fgfp3Z8rsvjRNbnkVwfu2ImRqE5D0LnY2QfDen18FSY9C573YU5XxSapdHZTZ2WolA==", + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-8.0.1.tgz", + "integrity": "sha512-z1e/HMG90obSGeidlli3hj7cbocou0/wa5HacvI3ASx34PecNjNQeaHNo5WIZpWofN9kgkqV1q5YvXe3F0FoPw==", "dev": true, "license": "MIT", "dependencies": { - "entities": "^6.0.0" + "entities": "^8.0.0" }, "funding": { "url": "https://github.com/inikulin/parse5?sponsor=1" @@ -5072,6 +5202,9 @@ "cpu": [ "arm64" ], + "libc": [ + "glibc" + ], "license": "MPL-2.0", "optional": true, "os": [ @@ -5092,6 +5225,9 @@ "cpu": [ "arm64" ], + "libc": [ + "musl" + ], "license": "MPL-2.0", "optional": true, "os": [ @@ -5112,6 +5248,9 @@ "cpu": [ "x64" ], + "libc": [ + "glibc" + ], "license": "MPL-2.0", "optional": true, "os": [ @@ -5132,6 +5271,9 @@ "cpu": [ "x64" ], + "libc": [ + "musl" + ], "license": "MPL-2.0", "optional": true, "os": [ @@ -5271,9 +5413,9 @@ "license": "MIT" }, "node_modules/nanoid": { - "version": "3.3.11", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", - "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "version": "3.3.12", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz", + "integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==", "funding": [ { "type": "github", @@ -5296,9 +5438,9 @@ "license": "MIT" }, "node_modules/node-releases": { - "version": "2.0.37", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.37.tgz", - "integrity": "sha512-1h5gKZCF+pO/o3Iqt5Jp7wc9rH3eJJ0+nh/CIoiRwjRxde/hAHyLPXYN4V3CqKAbiZPSeJFSWHmJsbkicta0Eg==", + "version": "2.0.38", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.38.tgz", + "integrity": "sha512-3qT/88Y3FbH/Kx4szpQQ4HzUbVrHPKTLVpVocKiLfoYvw9XSGOX2FmD2d6DrXbVYyAQTF2HeF6My8jmzx7/CRw==", "license": "MIT" }, "node_modules/normalize-path": { @@ -5515,9 +5657,9 @@ } }, "node_modules/prettier": { - "version": "3.8.1", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.1.tgz", - "integrity": "sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==", + "version": "3.8.3", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.3.tgz", + "integrity": "sha512-7igPTM53cGHMW8xWuVTydi2KO233VFiTNyF5hLJqpilHfmn8C8gPf+PS7dUT64YcXFbiMGZxS9pCSxL/Dxm/Jw==", "license": "MIT", "bin": { "prettier": "bin/prettier.cjs" @@ -5677,9 +5819,9 @@ } }, "node_modules/rollup": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.60.1.tgz", - "integrity": "sha512-VmtB2rFU/GroZ4oL8+ZqXgSA38O6GR8KSIvWmEFv63pQ0G6KaBH9s07PO8XTXP4vI+3UJUEypOfjkGfmSBBR0w==", + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.60.3.tgz", + "integrity": "sha512-pAQK9HalE84QSm4Po3EmWIZPd3FnjkShVkiMlz1iligWYkWQ7wHYd1PF/T7QZ5TVSD6uSTon5gBVMSM4JfBV+A==", "license": "MIT", "dependencies": { "@types/estree": "1.0.8" @@ -5692,31 +5834,31 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.60.1", - "@rollup/rollup-android-arm64": "4.60.1", - "@rollup/rollup-darwin-arm64": "4.60.1", - "@rollup/rollup-darwin-x64": "4.60.1", - "@rollup/rollup-freebsd-arm64": "4.60.1", - "@rollup/rollup-freebsd-x64": "4.60.1", - "@rollup/rollup-linux-arm-gnueabihf": "4.60.1", - "@rollup/rollup-linux-arm-musleabihf": "4.60.1", - "@rollup/rollup-linux-arm64-gnu": "4.60.1", - "@rollup/rollup-linux-arm64-musl": "4.60.1", - "@rollup/rollup-linux-loong64-gnu": "4.60.1", - "@rollup/rollup-linux-loong64-musl": "4.60.1", - "@rollup/rollup-linux-ppc64-gnu": "4.60.1", - "@rollup/rollup-linux-ppc64-musl": "4.60.1", - "@rollup/rollup-linux-riscv64-gnu": "4.60.1", - "@rollup/rollup-linux-riscv64-musl": "4.60.1", - "@rollup/rollup-linux-s390x-gnu": "4.60.1", - "@rollup/rollup-linux-x64-gnu": "4.60.1", - "@rollup/rollup-linux-x64-musl": "4.60.1", - "@rollup/rollup-openbsd-x64": "4.60.1", - "@rollup/rollup-openharmony-arm64": "4.60.1", - "@rollup/rollup-win32-arm64-msvc": "4.60.1", - "@rollup/rollup-win32-ia32-msvc": "4.60.1", - "@rollup/rollup-win32-x64-gnu": "4.60.1", - "@rollup/rollup-win32-x64-msvc": "4.60.1", + "@rollup/rollup-android-arm-eabi": "4.60.3", + "@rollup/rollup-android-arm64": "4.60.3", + "@rollup/rollup-darwin-arm64": "4.60.3", + "@rollup/rollup-darwin-x64": "4.60.3", + "@rollup/rollup-freebsd-arm64": "4.60.3", + "@rollup/rollup-freebsd-x64": "4.60.3", + "@rollup/rollup-linux-arm-gnueabihf": "4.60.3", + "@rollup/rollup-linux-arm-musleabihf": "4.60.3", + "@rollup/rollup-linux-arm64-gnu": "4.60.3", + "@rollup/rollup-linux-arm64-musl": "4.60.3", + "@rollup/rollup-linux-loong64-gnu": "4.60.3", + "@rollup/rollup-linux-loong64-musl": "4.60.3", + "@rollup/rollup-linux-ppc64-gnu": "4.60.3", + "@rollup/rollup-linux-ppc64-musl": "4.60.3", + "@rollup/rollup-linux-riscv64-gnu": "4.60.3", + "@rollup/rollup-linux-riscv64-musl": "4.60.3", + "@rollup/rollup-linux-s390x-gnu": "4.60.3", + "@rollup/rollup-linux-x64-gnu": "4.60.3", + "@rollup/rollup-linux-x64-musl": "4.60.3", + "@rollup/rollup-openbsd-x64": "4.60.3", + "@rollup/rollup-openharmony-arm64": "4.60.3", + "@rollup/rollup-win32-arm64-msvc": "4.60.3", + "@rollup/rollup-win32-ia32-msvc": "4.60.3", + "@rollup/rollup-win32-x64-gnu": "4.60.3", + "@rollup/rollup-win32-x64-msvc": "4.60.3", "fsevents": "~2.3.2" } }, @@ -5883,9 +6025,9 @@ "license": "MIT" }, "node_modules/tapable": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.2.tgz", - "integrity": "sha512-1MOpMXuhGzGL5TTCZFItxCc0AARf1EZFQkGqMm7ERKj8+Hgr5oLvJOVFcC+lRmR8hCe2S3jC4T5D7Vg/d7/fhA==", + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.3.tgz", + "integrity": "sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==", "dev": true, "license": "MIT", "engines": { @@ -5916,9 +6058,9 @@ "license": "MIT" }, "node_modules/tinyexec": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.1.1.tgz", - "integrity": "sha512-VKS/ZaQhhkKFMANmAOhhXVoIfBXblQxGX1myCQ2faQrfmobMftXeJPcZGp0gS07ocvGJWDLZGyOZDadDBqYIJg==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.1.2.tgz", + "integrity": "sha512-dAqSqE/RabpBKI8+h26GfLq6Vb3JVXs30XYQjdMjaj/c2tS8IYYMbIzP599KtRj7c57/wYApb3QjgRgXmrCukA==", "dev": true, "license": "MIT", "engines": { @@ -5926,13 +6068,13 @@ } }, "node_modules/tinyglobby": { - "version": "0.2.15", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", - "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "version": "0.2.16", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz", + "integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==", "license": "MIT", "dependencies": { "fdir": "^6.5.0", - "picomatch": "^4.0.3" + "picomatch": "^4.0.4" }, "engines": { "node": ">=12.0.0" @@ -5952,22 +6094,22 @@ } }, "node_modules/tldts": { - "version": "7.0.28", - "resolved": "https://registry.npmjs.org/tldts/-/tldts-7.0.28.tgz", - "integrity": "sha512-+Zg3vWhRUv8B1maGSTFdev9mjoo8Etn2Ayfs4cnjlD3CsGkxXX4QyW3j2WJ0wdjYcYmy7Lx2RDsZMhgCWafKIw==", + "version": "7.0.30", + "resolved": "https://registry.npmjs.org/tldts/-/tldts-7.0.30.tgz", + "integrity": "sha512-ELrFxuqsDdHUwoh0XxDbxuLD3Wnz49Z57IFvTtvWy1hJdcMZjXLIuonjilCiWHlT2GbE4Wlv1wKVTzDFnXH1aw==", "dev": true, "license": "MIT", "dependencies": { - "tldts-core": "^7.0.28" + "tldts-core": "^7.0.30" }, "bin": { "tldts": "bin/cli.js" } }, "node_modules/tldts-core": { - "version": "7.0.28", - "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.0.28.tgz", - "integrity": "sha512-7W5Efjhsc3chVdFhqtaU0KtK32J37Zcr9RKtID54nG+tIpcY79CQK/veYPODxtD/LJ4Lue66jvrQzIX2Z2/pUQ==", + "version": "7.0.30", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.0.30.tgz", + "integrity": "sha512-uiHN8PIB1VmWyS98eZYja4xzlYqeFZVjb4OuYlJQnZAuJhMw4PbKQOKgHKhBdJR3FE/t5mUQ1Kd80++B+qhD1Q==", "dev": true, "license": "MIT" }, @@ -6096,15 +6238,15 @@ } }, "node_modules/ufo": { - "version": "1.6.3", - "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.3.tgz", - "integrity": "sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q==", + "version": "1.6.4", + "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.4.tgz", + "integrity": "sha512-JFNbkD1Svwe0KvGi8GOeLcP4kAWQ609twvCdcHxq1oSL8svv39ZuSvajcD8B+5D0eL4+s1Is2D/O6KN3qcTeRA==", "license": "MIT" }, "node_modules/undici": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/undici/-/undici-7.24.7.tgz", - "integrity": "sha512-H/nlJ/h0ggGC+uRL3ovD+G0i4bqhvsDOpbDv7At5eFLlj2b41L8QliGbnl2H7SnDiYhENphh1tQFJZf+MyfLsQ==", + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.25.0.tgz", + "integrity": "sha512-xXnp4kTyor2Zq+J1FfPI6Eq3ew5h6Vl0F/8d9XU5zZQf1tX9s2Su1/3PiMmUANFULpmksxkClamIZcaUqryHsQ==", "license": "MIT", "engines": { "node": ">=20.18.1" diff --git a/tanstack-ui/.understand-anything/.understandignore b/tanstack-ui/.understand-anything/.understandignore new file mode 100644 index 00000000..e8bb4ac8 --- /dev/null +++ b/tanstack-ui/.understand-anything/.understandignore @@ -0,0 +1,6 @@ +# .understandignore — patterns for files/dirs to exclude from analysis +# Built-in defaults (always excluded): node_modules/, .git/, dist/, build/, *.lock + +# Exclude static assets +public/ +src/assets/ diff --git a/tanstack-ui/.understand-anything/knowledge-graph.json b/tanstack-ui/.understand-anything/knowledge-graph.json new file mode 100644 index 00000000..b9dd9c39 --- /dev/null +++ b/tanstack-ui/.understand-anything/knowledge-graph.json @@ -0,0 +1,1617 @@ +{ + "version": "1.0.0", + "project": { + "name": "tanstack-ui", + "languages": [ + "css", + "javascript", + "typescript" + ], + "frameworks": [ + "React", + "TanStack Router", + "TanStack Query", + "Vite", + "Tailwind CSS", + "Vitest" + ], + "description": "A React 19 job application tracker UI using TanStack Router for file-based routing and TanStack Query v5 for server state management, paired with nest-api on port 5050.", + "analyzedAt": "2026-05-04T21:34:29.986451+00:00", + "gitCommitHash": "e5054096efec8165e7d4cfe11fc103f77ad2d98f" + }, + "nodes": [ + { + "id": "file:src/main.tsx", + "type": "file", + "name": "main.tsx", + "filePath": "src/main.tsx", + "summary": "Application entry point that mounts the React app with QueryClientProvider and RouterProvider, configuring TanStack Query with 1-minute stale time and 1 retry.", + "tags": [ + "entry-point", + "react", + "bootstrap", + "provider" + ], + "complexity": "simple" + }, + { + "id": "file:src/router.ts", + "type": "file", + "name": "router.ts", + "filePath": "src/router.ts", + "summary": "Creates and exports the TanStack Router instance from the auto-generated route tree, with TypeScript module augmentation for router type registration.", + "tags": [ + "router", + "entry-point", + "tanstack-router", + "configuration" + ], + "complexity": "simple" + }, + { + "id": "file:src/routeTree.gen.ts", + "type": "file", + "name": "routeTree.gen.ts", + "filePath": "src/routeTree.gen.ts", + "summary": "Auto-generated TanStack Router route tree that composes all file-based routes into a typed hierarchy. Do not edit manually.", + "tags": [ + "generated", + "router", + "route-tree", + "tanstack-router" + ], + "complexity": "moderate", + "languageNotes": "Generated by @tanstack/router-plugin from Vite; updates automatically on dev server changes." + }, + { + "id": "file:src/types/application.ts", + "type": "file", + "name": "application.ts", + "filePath": "src/types/application.ts", + "summary": "Central TypeScript type definitions for the application domain: Application, InterviewStage, filter/sort state, pagination, history entries, and all input/output interfaces used across the UI.", + "tags": [ + "type-definition", + "data-model", + "domain", + "shared-types" + ], + "complexity": "moderate" + }, + { + "id": "file:src/services/api.ts", + "type": "file", + "name": "api.ts", + "filePath": "src/services/api.ts", + "summary": "HTTP client service wrapping all REST API calls to the NestJS backend via the /api proxy, covering applications CRUD, interview stages, history, and CSV import/export.", + "tags": [ + "service", + "api-client", + "http", + "fetch" + ], + "complexity": "complex" + }, + { + "id": "file:src/queries/queryKeys.ts", + "type": "file", + "name": "queryKeys.ts", + "filePath": "src/queries/queryKeys.ts", + "summary": "TanStack Query key factory pattern that generates hierarchical, typed cache keys for application list, detail, and history queries, enabling granular cache invalidation.", + "tags": [ + "query-keys", + "cache", + "tanstack-query", + "utility" + ], + "complexity": "simple", + "languageNotes": "Uses const assertion for typed tuple keys compatible with TanStack Query v5 invalidation." + }, + { + "id": "file:src/queries/applicationQueries.ts", + "type": "file", + "name": "applicationQueries.ts", + "filePath": "src/queries/applicationQueries.ts", + "summary": "TanStack Query hooks for reading application data: useApplications (paginated list with filters), useApplication (single record), and useApplicationHistory (change history).", + "tags": [ + "hook", + "tanstack-query", + "data-fetching", + "applications" + ], + "complexity": "simple" + }, + { + "id": "file:src/queries/applicationMutations.ts", + "type": "file", + "name": "applicationMutations.ts", + "filePath": "src/queries/applicationMutations.ts", + "summary": "TanStack Query mutation hooks for all write operations: create, update, delete, archive/restore applications, restore to history version, import CSV, and interview stage CRUD.", + "tags": [ + "hook", + "tanstack-query", + "mutations", + "applications" + ], + "complexity": "complex" + }, + { + "id": "file:src/hooks/useFilters.ts", + "type": "file", + "name": "useFilters.ts", + "filePath": "src/hooks/useFilters.ts", + "summary": "React hook managing filter state (status multi-select, category, source, skills match, archived toggle) with individual setters, clear function, and derived active-filter counts.", + "tags": [ + "hook", + "filter", + "state-management", + "applications" + ], + "complexity": "moderate" + }, + { + "id": "file:src/hooks/useSorting.ts", + "type": "file", + "name": "useSorting.ts", + "filePath": "src/hooks/useSorting.ts", + "summary": "React hook managing sort state with setSortBy, setSortDir, and toggleSortDir helpers; defaults to updatedAt descending.", + "tags": [ + "hook", + "sorting", + "state-management", + "applications" + ], + "complexity": "simple" + }, + { + "id": "file:src/lib/utils.ts", + "type": "file", + "name": "utils.ts", + "filePath": "src/lib/utils.ts", + "summary": "Pure utility functions: cn (class merging), formatDate, formatCurrency, formatSalaryRange, getDaysUntil, isOverdue, getTodayDate \u2014 shared across all UI components.", + "tags": [ + "utility", + "formatting", + "date", + "shared" + ], + "complexity": "moderate" + }, + { + "id": "file:src/lib/utils.test.ts", + "type": "file", + "name": "utils.test.ts", + "filePath": "src/lib/utils.test.ts", + "summary": "Vitest unit tests for the utility functions in lib/utils.ts, covering date formatting, currency formatting, and deadline calculation edge cases.", + "tags": [ + "test", + "vitest", + "utility", + "unit-test" + ], + "complexity": "simple" + }, + { + "id": "file:src/lib/constants.ts", + "type": "file", + "name": "constants.ts", + "filePath": "src/lib/constants.ts", + "summary": "Shared UI constants: APPLICATION_STATUSES, COMPANY_CATEGORIES, JOB_SOURCES label/value pairs, STATUS_COLORS Tailwind class map, and DEFAULT_INTERVIEW_STAGES list.", + "tags": [ + "constants", + "configuration", + "domain", + "ui" + ], + "complexity": "moderate" + }, + { + "id": "file:src/index.css", + "type": "file", + "name": "index.css", + "filePath": "src/index.css", + "summary": "Global CSS entry that imports Tailwind CSS v4 with custom color theme variables for primary (blue), success (green), warning (yellow), and danger (red) palettes.", + "tags": [ + "css", + "tailwind", + "theme", + "global-styles" + ], + "complexity": "moderate" + }, + { + "id": "file:index.html", + "type": "file", + "name": "index.html", + "filePath": "index.html", + "summary": "Vite HTML entry point that bootstraps the React app via the src/main.tsx module script, setting the page title to 'Application Tracker'.", + "tags": [ + "entry-point", + "html", + "vite", + "markup" + ], + "complexity": "simple" + }, + { + "id": "config:vite.config.ts", + "type": "config", + "name": "vite.config.ts", + "filePath": "vite.config.ts", + "summary": "Vite configuration with React plugin, TanStack Router file-based routing plugin, and /api proxy to localhost:5050 for the NestJS backend.", + "tags": [ + "configuration", + "vite", + "build-system", + "proxy" + ], + "complexity": "simple" + }, + { + "id": "config:tsconfig.json", + "type": "config", + "name": "tsconfig.json", + "filePath": "tsconfig.json", + "summary": "TypeScript compiler configuration with strict mode targeting ES2022 modules, React JSX transform, and Vite bundler mode.", + "tags": [ + "configuration", + "typescript", + "build-system" + ], + "complexity": "simple" + }, + { + "id": "config:tsconfig.node.json", + "type": "config", + "name": "tsconfig.node.json", + "filePath": "tsconfig.node.json", + "summary": "TypeScript configuration for Node.js tooling files (vite.config.ts, eslint.config.js) with bundler module resolution.", + "tags": [ + "configuration", + "typescript", + "build-system" + ], + "complexity": "simple" + }, + { + "id": "config:package.json", + "type": "config", + "name": "package.json", + "filePath": "package.json", + "summary": "Project manifest for tanstack-ui defining React 19, TanStack Router, TanStack Query v5, Tailwind CSS v4, Vitest dependencies and dev/build/test scripts.", + "tags": [ + "configuration", + "dependencies", + "npm", + "manifest" + ], + "complexity": "simple" + }, + { + "id": "document:CLAUDE.md", + "type": "document", + "name": "CLAUDE.md", + "filePath": "CLAUDE.md", + "summary": "Stack-specific developer guidance documenting the NestJS+TanStack pairing, port assignments, TanStack Router routing patterns, and known gotchas with DI and Zod.", + "tags": [ + "documentation", + "development", + "configuration" + ], + "complexity": "simple" + }, + { + "id": "file:src/routes/__root.tsx", + "type": "file", + "name": "__root.tsx", + "filePath": "src/routes/__root.tsx", + "summary": "TanStack Router root route that renders the global layout shell with Header component and Outlet for child routes, applying the full-height gray background.", + "tags": [ + "route", + "layout", + "tanstack-router", + "root" + ], + "complexity": "simple" + }, + { + "id": "file:src/routes/index.tsx", + "type": "file", + "name": "index.tsx", + "filePath": "src/routes/index.tsx", + "summary": "Home route (/) rendering the application list page with integrated filter/sort state, pagination, and archive/delete/import actions via TanStack Query mutations.", + "tags": [ + "route", + "component", + "applications", + "list-view" + ], + "complexity": "complex" + }, + { + "id": "file:src/routes/applications/$id.tsx", + "type": "file", + "name": "$id.tsx", + "filePath": "src/routes/applications/$id.tsx", + "summary": "Dynamic route for editing an existing application by ID (/applications/:id), delegating rendering to ApplicationEdit component.", + "tags": [ + "route", + "dynamic-route", + "applications", + "edit" + ], + "complexity": "simple" + }, + { + "id": "file:src/routes/applications/new.tsx", + "type": "file", + "name": "new.tsx", + "filePath": "src/routes/applications/new.tsx", + "summary": "Static route for creating a new application (/applications/new), rendering ApplicationEdit without an applicationId for create mode.", + "tags": [ + "route", + "applications", + "create", + "form" + ], + "complexity": "simple" + }, + { + "id": "file:src/components/common/Header.tsx", + "type": "file", + "name": "Header.tsx", + "filePath": "src/components/common/Header.tsx", + "summary": "Application header with site logo/title, dark mode toggle (persisted to localStorage), and Add Application navigation button.", + "tags": [ + "component", + "navigation", + "header", + "dark-mode" + ], + "complexity": "moderate" + }, + { + "id": "file:src/components/common/index.ts", + "type": "file", + "name": "index.ts", + "filePath": "src/components/common/index.ts", + "summary": "Barrel file re-exporting all common layout components (Header) for clean import paths.", + "tags": [ + "barrel", + "exports", + "common" + ], + "complexity": "simple" + }, + { + "id": "file:src/components/applications/ApplicationList.tsx", + "type": "file", + "name": "ApplicationList.tsx", + "filePath": "src/components/applications/ApplicationList.tsx", + "summary": "Renders a paginated list of ApplicationCard components with loading skeletons, empty state handling (with/without filters), and Pagination controls.", + "tags": [ + "component", + "list", + "applications", + "pagination" + ], + "complexity": "moderate" + }, + { + "id": "file:src/components/applications/ApplicationCard.tsx", + "type": "file", + "name": "ApplicationCard.tsx", + "filePath": "src/components/applications/ApplicationCard.tsx", + "summary": "Card component for a single job application showing company/position info, status badge, interview stage progress, offer deadline countdown, and archive/delete action menu with confirm dialog.", + "tags": [ + "component", + "card", + "applications", + "action-menu" + ], + "complexity": "complex" + }, + { + "id": "file:src/components/applications/FilterBar.tsx", + "type": "file", + "name": "FilterBar.tsx", + "filePath": "src/components/applications/FilterBar.tsx", + "summary": "Filter toolbar with status, category, source, skills match dropdowns, archived toggle, sort controls, result count display, and CSV import/export buttons.", + "tags": [ + "component", + "filter", + "toolbar", + "applications" + ], + "complexity": "complex" + }, + { + "id": "file:src/components/applications/HistoryPanel.tsx", + "type": "file", + "name": "HistoryPanel.tsx", + "filePath": "src/components/applications/HistoryPanel.tsx", + "summary": "Slide-over panel displaying the application change history with relative timestamps, expandable field diffs, and point-in-time restore functionality.", + "tags": [ + "component", + "history", + "side-panel", + "applications" + ], + "complexity": "complex" + }, + { + "id": "file:src/components/applications/ImportModal.tsx", + "type": "file", + "name": "ImportModal.tsx", + "filePath": "src/components/applications/ImportModal.tsx", + "summary": "CSV import modal with file selection, upload via importApplicationsCsv mutation, and result summary showing imported/skipped/error counts with per-row error details.", + "tags": [ + "component", + "modal", + "csv-import", + "applications" + ], + "complexity": "moderate" + }, + { + "id": "file:src/components/applications/FieldDiff.tsx", + "type": "file", + "name": "FieldDiff.tsx", + "filePath": "src/components/applications/FieldDiff.tsx", + "summary": "Displays a list of field changes between history snapshots, formatting old/new values per field for readable diff display in the HistoryPanel.", + "tags": [ + "component", + "diff", + "history", + "utility" + ], + "complexity": "simple" + }, + { + "id": "file:src/components/applications/index.ts", + "type": "file", + "name": "index.ts", + "filePath": "src/components/applications/index.ts", + "summary": "Barrel file re-exporting all application-domain components for clean import paths.", + "tags": [ + "barrel", + "exports", + "applications" + ], + "complexity": "simple" + }, + { + "id": "file:src/components/applications/ApplicationEdit.tsx", + "type": "file", + "name": "ApplicationEdit.tsx", + "filePath": "src/components/applications/ApplicationEdit.tsx", + "summary": "Large form component for creating and editing job applications, with sections for company details, application metadata, salary range, notes, interview stages list, and history panel toggle; handles both create and edit modes.", + "tags": [ + "component", + "form", + "applications", + "crud" + ], + "complexity": "complex" + }, + { + "id": "file:src/components/interviews/InterviewStageList.tsx", + "type": "file", + "name": "InterviewStageList.tsx", + "filePath": "src/components/interviews/InterviewStageList.tsx", + "summary": "Manages a sorted list of interview stages with progress bar, inline editing via InterviewStageItem, add/edit via InterviewStageForm modal, and a one-click 'Add Default Stages' shortcut.", + "tags": [ + "component", + "interview-stages", + "list", + "form" + ], + "complexity": "complex" + }, + { + "id": "file:src/components/interviews/InterviewStageItem.tsx", + "type": "file", + "name": "InterviewStageItem.tsx", + "filePath": "src/components/interviews/InterviewStageItem.tsx", + "summary": "Row component for a single interview stage showing name, order, completion checkbox, completed date, performance rating display, and edit/delete actions.", + "tags": [ + "component", + "interview-stages", + "item", + "row" + ], + "complexity": "moderate" + }, + { + "id": "file:src/components/interviews/InterviewStageForm.tsx", + "type": "file", + "name": "InterviewStageForm.tsx", + "filePath": "src/components/interviews/InterviewStageForm.tsx", + "summary": "Modal form for creating or editing an interview stage with fields for name, order, completion status, completed date, notes, and performance rating.", + "tags": [ + "component", + "form", + "interview-stages", + "modal" + ], + "complexity": "complex" + }, + { + "id": "file:src/components/interviews/InlineInterviewStageForm.tsx", + "type": "file", + "name": "InlineInterviewStageForm.tsx", + "filePath": "src/components/interviews/InlineInterviewStageForm.tsx", + "summary": "Compact inline form for quickly adding a new interview stage without opening a modal, used within the stages list context.", + "tags": [ + "component", + "form", + "interview-stages", + "inline" + ], + "complexity": "moderate" + }, + { + "id": "file:src/components/interviews/index.ts", + "type": "file", + "name": "index.ts", + "filePath": "src/components/interviews/index.ts", + "summary": "Barrel file re-exporting all interview-stage components for clean import paths.", + "tags": [ + "barrel", + "exports", + "interview-stages" + ], + "complexity": "simple" + }, + { + "id": "file:src/components/ui/Button.tsx", + "type": "file", + "name": "Button.tsx", + "filePath": "src/components/ui/Button.tsx", + "summary": "Reusable button component with four variants (primary, secondary, danger, ghost) and three sizes, implemented with forwardRef for form integration.", + "tags": [ + "component", + "ui", + "button", + "design-system" + ], + "complexity": "simple" + }, + { + "id": "file:src/components/ui/Badge.tsx", + "type": "file", + "name": "Badge.tsx", + "filePath": "src/components/ui/Badge.tsx", + "summary": "Status badge component that displays a colored pill for ApplicationStatus values, using STATUS_COLORS constants for Tailwind class lookup.", + "tags": [ + "component", + "ui", + "badge", + "status" + ], + "complexity": "simple" + }, + { + "id": "file:src/components/ui/Card.tsx", + "type": "file", + "name": "Card.tsx", + "filePath": "src/components/ui/Card.tsx", + "summary": "Card layout component with optional hover shadow, composed of Card, CardHeader, CardContent, and CardFooter sub-components.", + "tags": [ + "component", + "ui", + "card", + "layout" + ], + "complexity": "simple" + }, + { + "id": "file:src/components/ui/Input.tsx", + "type": "file", + "name": "Input.tsx", + "filePath": "src/components/ui/Input.tsx", + "summary": "Form input and textarea components with consistent label, error state, and helper text styling for the design system.", + "tags": [ + "component", + "ui", + "input", + "form" + ], + "complexity": "moderate" + }, + { + "id": "file:src/components/ui/Select.tsx", + "type": "file", + "name": "Select.tsx", + "filePath": "src/components/ui/Select.tsx", + "summary": "Dropdown select component with label and placeholder support, accepting options as value/label pairs for consistent filtering UI.", + "tags": [ + "component", + "ui", + "select", + "form" + ], + "complexity": "simple" + }, + { + "id": "file:src/components/ui/Modal.tsx", + "type": "file", + "name": "Modal.tsx", + "filePath": "src/components/ui/Modal.tsx", + "summary": "Accessible modal dialog with backdrop, Escape key handling, body scroll lock, and four size variants; includes ConfirmDialog compound component for destructive action confirmation.", + "tags": [ + "component", + "ui", + "modal", + "accessibility" + ], + "complexity": "moderate" + }, + { + "id": "file:src/components/ui/Pagination.tsx", + "type": "file", + "name": "Pagination.tsx", + "filePath": "src/components/ui/Pagination.tsx", + "summary": "Pagination control component showing previous/next buttons and page number range for navigating paginated data sets.", + "tags": [ + "component", + "ui", + "pagination", + "navigation" + ], + "complexity": "moderate" + }, + { + "id": "file:src/components/ui/Rating.tsx", + "type": "file", + "name": "Rating.tsx", + "filePath": "src/components/ui/Rating.tsx", + "summary": "Star rating components: RatingDisplay (read-only star row) and RatingInput (interactive star picker with optional clear), both supporting configurable max and size.", + "tags": [ + "component", + "ui", + "rating", + "interactive" + ], + "complexity": "moderate" + }, + { + "id": "file:src/components/ui/Checkbox.tsx", + "type": "file", + "name": "Checkbox.tsx", + "filePath": "src/components/ui/Checkbox.tsx", + "summary": "Labeled checkbox component with consistent Tailwind styling for filter and form controls.", + "tags": [ + "component", + "ui", + "checkbox", + "form" + ], + "complexity": "simple" + }, + { + "id": "file:src/components/ui/EmptyState.tsx", + "type": "file", + "name": "EmptyState.tsx", + "filePath": "src/components/ui/EmptyState.tsx", + "summary": "Empty state placeholder component accepting an icon, title, description, and optional action button for zero-data scenarios.", + "tags": [ + "component", + "ui", + "empty-state", + "feedback" + ], + "complexity": "simple" + }, + { + "id": "file:src/components/ui/UrlFieldInput.tsx", + "type": "file", + "name": "UrlFieldInput.tsx", + "filePath": "src/components/ui/UrlFieldInput.tsx", + "summary": "URL input component with an external link icon button that opens the URL in a new tab when a valid URL is present.", + "tags": [ + "component", + "ui", + "input", + "url" + ], + "complexity": "simple" + }, + { + "id": "file:src/components/ui/index.ts", + "type": "file", + "name": "index.ts", + "filePath": "src/components/ui/index.ts", + "summary": "Barrel file re-exporting all UI design system components (Button, Badge, Card, Input, Select, Modal, Rating, Checkbox, EmptyState, Pagination, UrlFieldInput).", + "tags": [ + "barrel", + "exports", + "design-system", + "ui" + ], + "complexity": "simple" + }, + { + "id": "file:eslint.config.js", + "type": "file", + "name": "eslint.config.js", + "filePath": "eslint.config.js", + "summary": "ESLint flat config with TypeScript, React Hooks, and React Refresh plugins enforcing strict type-aware linting with zero warnings tolerance.", + "tags": [ + "configuration", + "linting", + "code-quality" + ], + "complexity": "moderate" + }, + { + "id": "file:postcss.config.js", + "type": "file", + "name": "postcss.config.js", + "filePath": "postcss.config.js", + "summary": "PostCSS configuration enabling the Tailwind CSS v4 PostCSS plugin for CSS processing.", + "tags": [ + "configuration", + "css", + "tailwind", + "build-system" + ], + "complexity": "simple" + }, + { + "id": "file:.understand-anything/.understandignore", + "type": "file", + "name": ".understandignore", + "filePath": ".understand-anything/.understandignore", + "summary": "Configuration file for the understand-anything tool specifying file patterns to exclude from knowledge graph analysis.", + "tags": [ + "configuration", + "tooling" + ], + "complexity": "simple" + }, + { + "id": "config:.auditconfig.json", + "type": "config", + "name": ".auditconfig.json", + "filePath": ".auditconfig.json", + "summary": "audit-ci configuration file for the tanstack-ui package specifying security vulnerability allowlist thresholds.", + "tags": [ + "configuration", + "security", + "audit" + ], + "complexity": "simple" + } + ], + "edges": [ + { + "source": "file:src/main.tsx", + "target": "file:src/router.ts", + "type": "imports", + "direction": "forward", + "weight": 0.7 + }, + { + "source": "file:src/router.ts", + "target": "file:src/routeTree.gen.ts", + "type": "imports", + "direction": "forward", + "weight": 0.7 + }, + { + "source": "file:src/queries/applicationQueries.ts", + "target": "file:src/services/api.ts", + "type": "imports", + "direction": "forward", + "weight": 0.7 + }, + { + "source": "file:src/queries/applicationQueries.ts", + "target": "file:src/types/application.ts", + "type": "imports", + "direction": "forward", + "weight": 0.7 + }, + { + "source": "file:src/queries/applicationQueries.ts", + "target": "file:src/queries/queryKeys.ts", + "type": "imports", + "direction": "forward", + "weight": 0.7 + }, + { + "source": "file:src/queries/applicationMutations.ts", + "target": "file:src/services/api.ts", + "type": "imports", + "direction": "forward", + "weight": 0.7 + }, + { + "source": "file:src/queries/applicationMutations.ts", + "target": "file:src/types/application.ts", + "type": "imports", + "direction": "forward", + "weight": 0.7 + }, + { + "source": "file:src/queries/applicationMutations.ts", + "target": "file:src/queries/queryKeys.ts", + "type": "imports", + "direction": "forward", + "weight": 0.7 + }, + { + "source": "file:src/services/api.ts", + "target": "file:src/types/application.ts", + "type": "imports", + "direction": "forward", + "weight": 0.7 + }, + { + "source": "file:src/hooks/useFilters.ts", + "target": "file:src/types/application.ts", + "type": "imports", + "direction": "forward", + "weight": 0.7 + }, + { + "source": "file:src/hooks/useSorting.ts", + "target": "file:src/types/application.ts", + "type": "imports", + "direction": "forward", + "weight": 0.7 + }, + { + "source": "file:src/lib/constants.ts", + "target": "file:src/types/application.ts", + "type": "imports", + "direction": "forward", + "weight": 0.7 + }, + { + "source": "file:src/lib/utils.test.ts", + "target": "file:src/lib/utils.ts", + "type": "imports", + "direction": "forward", + "weight": 0.7 + }, + { + "source": "file:src/lib/utils.test.ts", + "target": "file:src/lib/utils.ts", + "type": "tested_by", + "direction": "forward", + "weight": 0.5 + }, + { + "source": "config:vite.config.ts", + "target": "file:src/main.tsx", + "type": "configures", + "direction": "forward", + "weight": 0.6 + }, + { + "source": "config:tsconfig.json", + "target": "file:src/main.tsx", + "type": "configures", + "direction": "forward", + "weight": 0.6 + }, + { + "source": "file:src/main.tsx", + "target": "file:src/index.css", + "type": "imports", + "direction": "forward", + "weight": 0.7 + }, + { + "source": "document:CLAUDE.md", + "target": "file:src/main.tsx", + "type": "documents", + "direction": "forward", + "weight": 0.5 + }, + { + "source": "file:src/routes/__root.tsx", + "target": "file:src/components/common/Header.tsx", + "type": "imports", + "direction": "forward", + "weight": 0.7 + }, + { + "source": "file:src/routes/index.tsx", + "target": "file:src/hooks/useFilters.ts", + "type": "imports", + "direction": "forward", + "weight": 0.7 + }, + { + "source": "file:src/routes/index.tsx", + "target": "file:src/hooks/useSorting.ts", + "type": "imports", + "direction": "forward", + "weight": 0.7 + }, + { + "source": "file:src/routes/index.tsx", + "target": "file:src/types/application.ts", + "type": "imports", + "direction": "forward", + "weight": 0.7 + }, + { + "source": "file:src/routes/index.tsx", + "target": "file:src/queries/applicationQueries.ts", + "type": "imports", + "direction": "forward", + "weight": 0.7 + }, + { + "source": "file:src/routes/index.tsx", + "target": "file:src/queries/applicationMutations.ts", + "type": "imports", + "direction": "forward", + "weight": 0.7 + }, + { + "source": "file:src/routes/index.tsx", + "target": "file:src/components/applications/index.ts", + "type": "imports", + "direction": "forward", + "weight": 0.7 + }, + { + "source": "file:src/routes/applications/$id.tsx", + "target": "file:src/components/applications/ApplicationEdit.tsx", + "type": "imports", + "direction": "forward", + "weight": 0.7 + }, + { + "source": "file:src/routes/applications/new.tsx", + "target": "file:src/components/applications/ApplicationEdit.tsx", + "type": "imports", + "direction": "forward", + "weight": 0.7 + }, + { + "source": "file:src/components/applications/ApplicationList.tsx", + "target": "file:src/components/applications/ApplicationCard.tsx", + "type": "imports", + "direction": "forward", + "weight": 0.7 + }, + { + "source": "file:src/components/applications/ApplicationCard.tsx", + "target": "file:src/types/application.ts", + "type": "imports", + "direction": "forward", + "weight": 0.7 + }, + { + "source": "file:src/components/applications/ApplicationCard.tsx", + "target": "file:src/lib/utils.ts", + "type": "imports", + "direction": "forward", + "weight": 0.7 + }, + { + "source": "file:src/components/applications/ApplicationCard.tsx", + "target": "file:src/lib/constants.ts", + "type": "imports", + "direction": "forward", + "weight": 0.7 + }, + { + "source": "file:src/components/applications/FilterBar.tsx", + "target": "file:src/types/application.ts", + "type": "imports", + "direction": "forward", + "weight": 0.7 + }, + { + "source": "file:src/components/applications/FilterBar.tsx", + "target": "file:src/lib/constants.ts", + "type": "imports", + "direction": "forward", + "weight": 0.7 + }, + { + "source": "file:src/components/applications/FilterBar.tsx", + "target": "file:src/services/api.ts", + "type": "imports", + "direction": "forward", + "weight": 0.7 + }, + { + "source": "file:src/components/applications/HistoryPanel.tsx", + "target": "file:src/types/application.ts", + "type": "imports", + "direction": "forward", + "weight": 0.7 + }, + { + "source": "file:src/components/applications/HistoryPanel.tsx", + "target": "file:src/queries/applicationQueries.ts", + "type": "imports", + "direction": "forward", + "weight": 0.7 + }, + { + "source": "file:src/components/applications/HistoryPanel.tsx", + "target": "file:src/queries/applicationMutations.ts", + "type": "imports", + "direction": "forward", + "weight": 0.7 + }, + { + "source": "file:src/components/applications/HistoryPanel.tsx", + "target": "file:src/components/applications/FieldDiff.tsx", + "type": "imports", + "direction": "forward", + "weight": 0.7 + }, + { + "source": "file:src/components/applications/ImportModal.tsx", + "target": "file:src/queries/applicationMutations.ts", + "type": "imports", + "direction": "forward", + "weight": 0.7 + }, + { + "source": "file:src/components/applications/ImportModal.tsx", + "target": "file:src/services/api.ts", + "type": "imports", + "direction": "forward", + "weight": 0.7 + }, + { + "source": "file:src/components/applications/ImportModal.tsx", + "target": "file:src/types/application.ts", + "type": "imports", + "direction": "forward", + "weight": 0.7 + }, + { + "source": "file:src/components/applications/index.ts", + "target": "file:src/components/applications/ApplicationCard.tsx", + "type": "exports", + "direction": "forward", + "weight": 0.8 + }, + { + "source": "file:src/components/applications/index.ts", + "target": "file:src/components/applications/ApplicationEdit.tsx", + "type": "exports", + "direction": "forward", + "weight": 0.8 + }, + { + "source": "file:src/components/applications/index.ts", + "target": "file:src/components/applications/ApplicationList.tsx", + "type": "exports", + "direction": "forward", + "weight": 0.8 + }, + { + "source": "file:src/components/applications/index.ts", + "target": "file:src/components/applications/FilterBar.tsx", + "type": "exports", + "direction": "forward", + "weight": 0.8 + }, + { + "source": "file:src/components/applications/index.ts", + "target": "file:src/components/applications/ImportModal.tsx", + "type": "exports", + "direction": "forward", + "weight": 0.8 + }, + { + "source": "file:src/components/common/index.ts", + "target": "file:src/components/common/Header.tsx", + "type": "exports", + "direction": "forward", + "weight": 0.8 + }, + { + "source": "file:src/components/interviews/InterviewStageList.tsx", + "target": "file:src/types/application.ts", + "type": "imports", + "direction": "forward", + "weight": 0.7 + }, + { + "source": "file:src/components/interviews/InterviewStageList.tsx", + "target": "file:src/components/interviews/InterviewStageItem.tsx", + "type": "imports", + "direction": "forward", + "weight": 0.7 + }, + { + "source": "file:src/components/interviews/InterviewStageList.tsx", + "target": "file:src/components/interviews/InterviewStageForm.tsx", + "type": "imports", + "direction": "forward", + "weight": 0.7 + }, + { + "source": "file:src/components/interviews/InterviewStageList.tsx", + "target": "file:src/lib/constants.ts", + "type": "imports", + "direction": "forward", + "weight": 0.7 + }, + { + "source": "file:src/components/interviews/InterviewStageItem.tsx", + "target": "file:src/types/application.ts", + "type": "imports", + "direction": "forward", + "weight": 0.7 + }, + { + "source": "file:src/components/interviews/InterviewStageForm.tsx", + "target": "file:src/types/application.ts", + "type": "imports", + "direction": "forward", + "weight": 0.7 + }, + { + "source": "file:src/components/interviews/InlineInterviewStageForm.tsx", + "target": "file:src/types/application.ts", + "type": "imports", + "direction": "forward", + "weight": 0.7 + }, + { + "source": "file:src/components/interviews/index.ts", + "target": "file:src/components/interviews/InterviewStageList.tsx", + "type": "exports", + "direction": "forward", + "weight": 0.8 + }, + { + "source": "file:src/components/interviews/index.ts", + "target": "file:src/components/interviews/InterviewStageItem.tsx", + "type": "exports", + "direction": "forward", + "weight": 0.8 + }, + { + "source": "file:src/components/interviews/index.ts", + "target": "file:src/components/interviews/InterviewStageForm.tsx", + "type": "exports", + "direction": "forward", + "weight": 0.8 + }, + { + "source": "file:src/components/ui/Button.tsx", + "target": "file:src/lib/utils.ts", + "type": "imports", + "direction": "forward", + "weight": 0.7 + }, + { + "source": "file:src/components/ui/Badge.tsx", + "target": "file:src/lib/utils.ts", + "type": "imports", + "direction": "forward", + "weight": 0.7 + }, + { + "source": "file:src/components/ui/Badge.tsx", + "target": "file:src/types/application.ts", + "type": "imports", + "direction": "forward", + "weight": 0.7 + }, + { + "source": "file:src/components/ui/Badge.tsx", + "target": "file:src/lib/constants.ts", + "type": "imports", + "direction": "forward", + "weight": 0.7 + }, + { + "source": "file:src/components/ui/Modal.tsx", + "target": "file:src/lib/utils.ts", + "type": "imports", + "direction": "forward", + "weight": 0.7 + }, + { + "source": "file:src/components/ui/Rating.tsx", + "target": "file:src/lib/utils.ts", + "type": "imports", + "direction": "forward", + "weight": 0.7 + }, + { + "source": "file:src/components/ui/index.ts", + "target": "file:src/components/ui/Button.tsx", + "type": "exports", + "direction": "forward", + "weight": 0.8 + }, + { + "source": "file:src/components/ui/index.ts", + "target": "file:src/components/ui/Badge.tsx", + "type": "exports", + "direction": "forward", + "weight": 0.8 + }, + { + "source": "file:src/components/ui/index.ts", + "target": "file:src/components/ui/Card.tsx", + "type": "exports", + "direction": "forward", + "weight": 0.8 + }, + { + "source": "file:src/components/ui/index.ts", + "target": "file:src/components/ui/Modal.tsx", + "type": "exports", + "direction": "forward", + "weight": 0.8 + }, + { + "source": "file:src/components/ui/index.ts", + "target": "file:src/components/ui/Rating.tsx", + "type": "exports", + "direction": "forward", + "weight": 0.8 + }, + { + "source": "file:index.html", + "target": "file:src/main.tsx", + "type": "imports", + "direction": "forward", + "weight": 0.7 + }, + { + "source": "config:tsconfig.node.json", + "target": "config:vite.config.ts", + "type": "configures", + "direction": "forward", + "weight": 0.6 + }, + { + "source": "config:package.json", + "target": "file:src/main.tsx", + "type": "configures", + "direction": "forward", + "weight": 0.6 + }, + { + "source": "file:src/components/ui/Input.tsx", + "target": "file:src/lib/utils.ts", + "type": "imports", + "direction": "forward", + "weight": 0.7 + }, + { + "source": "file:src/components/ui/Select.tsx", + "target": "file:src/lib/utils.ts", + "type": "imports", + "direction": "forward", + "weight": 0.7 + }, + { + "source": "file:src/components/ui/index.ts", + "target": "file:src/components/ui/Pagination.tsx", + "type": "exports", + "direction": "forward", + "weight": 0.8 + }, + { + "source": "file:src/components/ui/index.ts", + "target": "file:src/components/ui/Checkbox.tsx", + "type": "exports", + "direction": "forward", + "weight": 0.8 + }, + { + "source": "file:src/components/ui/index.ts", + "target": "file:src/components/ui/EmptyState.tsx", + "type": "exports", + "direction": "forward", + "weight": 0.8 + }, + { + "source": "file:src/components/ui/index.ts", + "target": "file:src/components/ui/UrlFieldInput.tsx", + "type": "exports", + "direction": "forward", + "weight": 0.8 + }, + { + "source": "file:src/components/ui/index.ts", + "target": "file:src/components/ui/Input.tsx", + "type": "exports", + "direction": "forward", + "weight": 0.8 + }, + { + "source": "file:src/components/ui/index.ts", + "target": "file:src/components/ui/Select.tsx", + "type": "exports", + "direction": "forward", + "weight": 0.8 + }, + { + "source": "config:package.json", + "target": "file:eslint.config.js", + "type": "configures", + "direction": "forward", + "weight": 0.6 + }, + { + "source": "config:package.json", + "target": "file:postcss.config.js", + "type": "configures", + "direction": "forward", + "weight": 0.6 + }, + { + "source": "config:.auditconfig.json", + "target": "config:package.json", + "type": "configures", + "direction": "forward", + "weight": 0.6 + }, + { + "source": "file:.understand-anything/.understandignore", + "target": "file:src/main.tsx", + "type": "related", + "direction": "forward", + "weight": 0.5 + } + ], + "layers": [ + { + "id": "layer:entry-bootstrap", + "name": "Entry & Bootstrap", + "description": "Application entry point, router setup, and generated route tree that bootstrap the React app.", + "nodeIds": [ + "file:src/main.tsx", + "file:src/router.ts", + "file:src/routeTree.gen.ts", + "file:index.html" + ] + }, + { + "id": "layer:routing", + "name": "Routing Layer", + "description": "TanStack Router file-based route definitions that map URLs to page components.", + "nodeIds": [ + "file:src/routes/__root.tsx", + "file:src/routes/index.tsx", + "file:src/routes/applications/$id.tsx", + "file:src/routes/applications/new.tsx" + ] + }, + { + "id": "layer:ui-components", + "name": "UI Components", + "description": "Application-specific React components organized by domain (applications, interviews) and common layout components.", + "nodeIds": [ + "file:src/components/applications/ApplicationCard.tsx", + "file:src/components/applications/ApplicationEdit.tsx", + "file:src/components/applications/ApplicationList.tsx", + "file:src/components/applications/FieldDiff.tsx", + "file:src/components/applications/FilterBar.tsx", + "file:src/components/applications/HistoryPanel.tsx", + "file:src/components/applications/ImportModal.tsx", + "file:src/components/applications/index.ts", + "file:src/components/common/Header.tsx", + "file:src/components/common/index.ts", + "file:src/components/interviews/InlineInterviewStageForm.tsx", + "file:src/components/interviews/InterviewStageForm.tsx", + "file:src/components/interviews/InterviewStageItem.tsx", + "file:src/components/interviews/InterviewStageList.tsx", + "file:src/components/interviews/index.ts" + ] + }, + { + "id": "layer:design-system", + "name": "Design System", + "description": "Primitive UI components (Button, Badge, Card, Input, Modal, etc.) forming the reusable component library.", + "nodeIds": [ + "file:src/components/ui/Badge.tsx", + "file:src/components/ui/Button.tsx", + "file:src/components/ui/Card.tsx", + "file:src/components/ui/Checkbox.tsx", + "file:src/components/ui/EmptyState.tsx", + "file:src/components/ui/Input.tsx", + "file:src/components/ui/Modal.tsx", + "file:src/components/ui/Pagination.tsx", + "file:src/components/ui/Rating.tsx", + "file:src/components/ui/Select.tsx", + "file:src/components/ui/UrlFieldInput.tsx", + "file:src/components/ui/index.ts" + ] + }, + { + "id": "layer:data-fetching", + "name": "Data Fetching Layer", + "description": "TanStack Query hooks (queries and mutations), query key factory, and the HTTP API client service.", + "nodeIds": [ + "file:src/queries/applicationMutations.ts", + "file:src/queries/applicationQueries.ts", + "file:src/queries/queryKeys.ts", + "file:src/services/api.ts" + ] + }, + { + "id": "layer:state-hooks", + "name": "State Hooks", + "description": "Custom React hooks for UI-local state management (filters and sorting).", + "nodeIds": [ + "file:src/hooks/useFilters.ts", + "file:src/hooks/useSorting.ts" + ] + }, + { + "id": "layer:types-domain", + "name": "Domain Types", + "description": "TypeScript interfaces and type definitions for the application domain shared across the entire UI.", + "nodeIds": [ + "file:src/types/application.ts" + ] + }, + { + "id": "layer:utilities", + "name": "Utilities & Constants", + "description": "Pure utility functions, display constants, and global styles shared across components.", + "nodeIds": [ + "file:src/lib/constants.ts", + "file:src/lib/utils.ts", + "file:src/lib/utils.test.ts", + "file:src/index.css" + ] + }, + { + "id": "layer:configuration", + "name": "Configuration", + "description": "Build tooling, TypeScript, ESLint, and package configuration files.", + "nodeIds": [ + "config:vite.config.ts", + "config:tsconfig.json", + "config:tsconfig.node.json", + "config:package.json", + "config:.auditconfig.json", + "file:eslint.config.js", + "file:postcss.config.js", + "file:.understand-anything/.understandignore" + ] + }, + { + "id": "layer:documentation", + "name": "Documentation", + "description": "Developer documentation and stack-specific guidance files.", + "nodeIds": [ + "document:CLAUDE.md" + ] + } + ], + "tour": [ + { + "order": 1, + "title": "Application Entry Point", + "description": "Start at src/main.tsx \u2014 the React app bootstraps here. It creates a QueryClient (TanStack Query) and mounts the RouterProvider (TanStack Router) inside a StrictMode shell. This is where all global providers are composed.", + "nodeIds": [ + "file:src/main.tsx", + "file:src/router.ts", + "file:src/routeTree.gen.ts" + ] + }, + { + "order": 2, + "title": "Domain Types", + "description": "Before reading any components, read src/types/application.ts. This single file defines every domain interface: Application, InterviewStage, filter/sort state, pagination, history, and all API input types. It is the shared contract for the entire UI.", + "nodeIds": [ + "file:src/types/application.ts" + ] + }, + { + "order": 3, + "title": "HTTP API Client", + "description": "src/services/api.ts wraps every backend call using the native Fetch API against the /api proxy (forwarded to nest-api at port 5050). It defines ApiError, a generic handleResponse handler, and all CRUD operations for applications, interview stages, history, and CSV import/export.", + "nodeIds": [ + "file:src/services/api.ts" + ] + }, + { + "order": 4, + "title": "TanStack Query Layer", + "description": "The query layer bridges the API client and components. queryKeys.ts provides a typed key factory for cache management. applicationQueries.ts wraps reads (useApplications, useApplication, useApplicationHistory). applicationMutations.ts wraps all writes and handles cache invalidation on success.", + "nodeIds": [ + "file:src/queries/queryKeys.ts", + "file:src/queries/applicationQueries.ts", + "file:src/queries/applicationMutations.ts" + ] + }, + { + "order": 5, + "title": "File-Based Routing", + "description": "TanStack Router uses file-based routing under src/routes/. The root layout (__root.tsx) renders Header + Outlet. The index route (/) renders the application list page. Dynamic routes /applications/$id and /applications/new delegate to ApplicationEdit in create vs edit mode.", + "nodeIds": [ + "file:src/routes/__root.tsx", + "file:src/routes/index.tsx", + "file:src/routes/applications/$id.tsx", + "file:src/routes/applications/new.tsx" + ] + }, + { + "order": 6, + "title": "State Hooks", + "description": "useFilters.ts and useSorting.ts encapsulate UI-local state for filtering and sorting the application list. They expose individual setters, a clear function, and derived counts that the index route wires into the FilterBar and API query params.", + "nodeIds": [ + "file:src/hooks/useFilters.ts", + "file:src/hooks/useSorting.ts" + ] + }, + { + "order": 7, + "title": "Application List View", + "description": "The list page composes FilterBar (filter/sort controls + CSV import/export buttons), ApplicationList (renders ApplicationCard items with loading/empty states), and ImportModal. It wires together the filter and sort hooks with TanStack Query for a fully reactive list experience.", + "nodeIds": [ + "file:src/components/applications/FilterBar.tsx", + "file:src/components/applications/ApplicationList.tsx", + "file:src/components/applications/ApplicationCard.tsx", + "file:src/components/applications/ImportModal.tsx" + ] + }, + { + "order": 8, + "title": "Application Edit Form", + "description": "ApplicationEdit.tsx is the largest component \u2014 a multi-section form for both creating and editing applications. It contains company info, application metadata, salary, notes, interview stage management, and a toggle for the HistoryPanel slide-over.", + "nodeIds": [ + "file:src/components/applications/ApplicationEdit.tsx", + "file:src/components/applications/HistoryPanel.tsx", + "file:src/components/applications/FieldDiff.tsx" + ] + }, + { + "order": 9, + "title": "Interview Stage Management", + "description": "InterviewStageList manages a sorted list with progress tracking. Each row is an InterviewStageItem with inline complete/rating controls. InterviewStageForm provides a modal for creating/editing stages. The 'Add Default Stages' shortcut populates a standard interview pipeline in one click.", + "nodeIds": [ + "file:src/components/interviews/InterviewStageList.tsx", + "file:src/components/interviews/InterviewStageItem.tsx", + "file:src/components/interviews/InterviewStageForm.tsx" + ] + }, + { + "order": 10, + "title": "Design System", + "description": "The src/components/ui/ directory provides the primitive building blocks: Button (4 variants), Badge (status colors), Card, Input/TextArea, Select, Modal/ConfirmDialog (accessible, portal-rendered), Rating (display and interactive), Checkbox, EmptyState, Pagination, and UrlFieldInput.", + "nodeIds": [ + "file:src/components/ui/Button.tsx", + "file:src/components/ui/Badge.tsx", + "file:src/components/ui/Modal.tsx", + "file:src/components/ui/Rating.tsx", + "file:src/components/ui/index.ts" + ] + }, + { + "order": 11, + "title": "Utilities & Constants", + "description": "src/lib/utils.ts provides pure formatting functions (dates, currency, salary range, deadline countdown). src/lib/constants.ts defines display labels and Tailwind color maps for statuses, categories, and sources. src/lib/utils.test.ts validates these helpers with Vitest.", + "nodeIds": [ + "file:src/lib/utils.ts", + "file:src/lib/constants.ts", + "file:src/lib/utils.test.ts" + ] + }, + { + "order": 12, + "title": "Build & Configuration", + "description": "Vite is configured with the TanStack Router plugin (auto-generates routeTree.gen.ts), the React plugin, a @/ path alias, and an /api proxy to localhost:5050. Tailwind CSS v4 is processed via PostCSS. TypeScript strict mode is enforced.", + "nodeIds": [ + "config:vite.config.ts", + "config:tsconfig.json", + "config:package.json" + ] + } + ] +} \ No newline at end of file diff --git a/tanstack-ui/.understand-anything/meta.json b/tanstack-ui/.understand-anything/meta.json new file mode 100644 index 00000000..b4b98547 --- /dev/null +++ b/tanstack-ui/.understand-anything/meta.json @@ -0,0 +1,6 @@ +{ + "lastAnalyzedAt": "2026-05-04T21:35:27.094765+00:00", + "gitCommitHash": "e5054096efec8165e7d4cfe11fc103f77ad2d98f", + "version": "1.0.0", + "analyzedFiles": 55 +} \ No newline at end of file diff --git a/vue-ui/.understand-anything/.understandignore b/vue-ui/.understand-anything/.understandignore new file mode 100644 index 00000000..433f312a --- /dev/null +++ b/vue-ui/.understand-anything/.understandignore @@ -0,0 +1,9 @@ +# .understandignore — patterns for files/dirs to exclude from analysis +# Built-in defaults (always excluded): node_modules/, .git/, dist/, build/, *.lock + +# Exclude test output and assets +test-results/ +playwright-report/ +playwright-report-vue-specific/ +public/ +*.tsbuildinfo diff --git a/vue-ui/.understand-anything/knowledge-graph.json b/vue-ui/.understand-anything/knowledge-graph.json new file mode 100644 index 00000000..aa525256 --- /dev/null +++ b/vue-ui/.understand-anything/knowledge-graph.json @@ -0,0 +1,515 @@ +{ + "version": "1.0.0", + "project": { + "name": "job-tracker-vue-ui", + "languages": ["TypeScript", "Vue", "CSS"], + "frameworks": ["Vue 3", "Vite", "Pinia", "Vue Router", "Tailwind CSS", "Vitest", "Playwright"], + "description": "Vue 3 + TypeScript single-page application for tracking job applications, featuring event-sourced undo/redo history, Pinia state management with Immer patches, and Tailwind CSS dark-mode UI. Pairs with nuxt-api backend (port 5040).", + "analyzedAt": "2026-05-04T00:00:00.000Z", + "gitCommitHash": "e5054096efec8165e7d4cfe11fc103f77ad2d98f" + }, + "nodes": [ + { + "id": "file:src/main.ts", + "type": "file", + "name": "main.ts", + "filePath": "src/main.ts", + "summary": "Application bootstrap: creates the Vue app instance, registers Pinia and Vue Router plugins, defines client-side routes (list, new, edit), enables Immer patch generation for undo/redo, and mounts to #app.", + "tags": ["entry-point", "config", "routing"] + }, + { + "id": "file:src/App.vue", + "type": "file", + "name": "App.vue", + "filePath": "src/App.vue", + "summary": "Root application shell component. Renders a fixed header with Vue logo, app title, dark-mode toggle (via useDarkMode composable), and an Add Application button. Hosts for page content.", + "tags": ["entry-point", "ui", "layout"] + }, + { + "id": "file:src/types/index.ts", + "type": "file", + "name": "types/index.ts", + "filePath": "src/types/index.ts", + "summary": "Barrel re-export of all shared domain types and constants from @shared/types (nuxt-api). Provides ApplicationStatus, CompanyCategory, JobSource, Application, InterviewStage, event-sourcing types (ImmerPatch, FieldChange, ApplicationEvent, ApplicationSnapshot), and lookup arrays APPLICATION_STATUSES, COMPANY_CATEGORIES, JOB_SOURCES.", + "tags": ["type-definition", "utility"] + }, + { + "id": "file:src/services/api.ts", + "type": "file", + "name": "services/api.ts", + "filePath": "src/services/api.ts", + "summary": "HTTP client layer. Exports applicationService (list, get, create, update, delete, archive, restore, recreate), interviewStageService (create, update, delete), eventService (list, append, restore, getLatestSnapshot), and checkHealth. All requests proxy to /api (forwarded to nuxt-api on port 5040). Handles 204 No Content responses.", + "tags": ["service", "api-client"] + }, + { + "id": "file:src/stores/applicationsList.ts", + "type": "file", + "name": "stores/applicationsList.ts", + "filePath": "src/stores/applicationsList.ts", + "summary": "Pinia setup store managing the paginated applications list. Holds applications array, total count, loading/error state, and FilterState. Provides fetchApplications, createApplication (also records creation event), updateApplication, deleteApplication, archiveApplication, restoreApplication, and pagination helpers (nextPage, prevPage, goToPage). Watches filters deeply to auto-refetch.", + "tags": ["service", "state", "pinia"] + }, + { + "id": "file:src/stores/applicationDetail.ts", + "type": "file", + "name": "stores/applicationDetail.ts", + "filePath": "src/stores/applicationDetail.ts", + "summary": "Pinia setup store for a single application in edit mode. Uses Immer produceWithPatches to record before/after state diffs for every mutation (update, delete, archive, restore, addInterviewStage, updateInterviewStage, deleteInterviewStage, toggleStageCompletion). Calls eventService.append to persist each change as a server-side event, then delegates to historyStore.addCommit. Guards mutations with isUndoRedoInProgress to avoid double-recording during undo/redo.", + "tags": ["service", "state", "pinia", "event-sourcing"] + }, + { + "id": "file:src/stores/history.ts", + "type": "file", + "name": "stores/history.ts", + "filePath": "src/stores/history.ts", + "summary": "Pinia setup store implementing per-application undo/redo via an event-sourcing commit log. Maintains commitsByApp (Map) and cursorByApp (Map). addCommit truncates redo stack. undo/redo apply Immer inversePatches/patches locally and sync diffs to the server via syncStateToServer (handles archive/restore toggling, field updates, and interview stage adds/updates/deletes). loadHistory fetches server events and rebuilds the local commit log.", + "tags": ["service", "state", "pinia", "event-sourcing"] + }, + { + "id": "file:src/composables/useDarkMode.ts", + "type": "file", + "name": "composables/useDarkMode.ts", + "filePath": "src/composables/useDarkMode.ts", + "summary": "Composable that manages dark mode preference. Reads/writes localStorage key 'app-theme', falls back to system prefers-color-scheme media query, listens for system changes (unless user has explicitly set preference). Toggles 'dark' class on document.documentElement. Cleaned up on component unmount.", + "tags": ["service", "utility", "composable"] + }, + { + "id": "file:src/utils/eventDescriptions.ts", + "type": "file", + "name": "utils/eventDescriptions.ts", + "filePath": "src/utils/eventDescriptions.ts", + "summary": "Pure utility functions for event sourcing. generateFieldChanges compares old/new Application states and returns FieldChange[] for changed keys. generateDescription produces human-readable strings for actions (create, update, delete, archive, restore, addStage, updateStage, deleteStage). Exports FIELD_LABELS mapping for display.", + "tags": ["utility", "event-sourcing"] + }, + { + "id": "file:src/views/ApplicationList.vue", + "type": "file", + "name": "views/ApplicationList.vue", + "filePath": "src/views/ApplicationList.vue", + "summary": "Page view for the applications list route (/). Fetches applications on mount via useApplicationsListStore. Renders FilterBar, a list of ApplicationCard components, Pagination, EmptyState, and a ConfirmDialog for delete confirmation. Handles archive, restore, and delete actions delegating to the list store.", + "tags": ["ui", "routing", "view"] + }, + { + "id": "file:src/views/ApplicationEdit.vue", + "type": "file", + "name": "views/ApplicationEdit.vue", + "filePath": "src/views/ApplicationEdit.vue", + "summary": "Page view for both create (/applications/new) and edit (/applications/:id) modes. Contains full application form with individual field refs, snapshot-based dirty tracking, validation, and save/discard/delete/archive/restore actions. In edit mode: integrates UndoRedoBar, HistoryPanel, keyboard shortcuts (Ctrl+Z/Y), and watches detailStore.application for undo/redo sync. Prevents navigation away with unsaved changes via onBeforeRouteLeave + skipNavGuard pattern.", + "tags": ["ui", "routing", "view"] + }, + { + "id": "file:src/components/ApplicationCard.vue", + "type": "file", + "name": "components/ApplicationCard.vue", + "filePath": "src/components/ApplicationCard.vue", + "summary": "Card component displaying a single application in the list. Shows company name, position title, StatusBadge, date applied, company category, skills match (RatingDisplay), interview progress (N/M stages for interviewing status), and offer due date urgency indicator. Includes a dropdown action menu (archive/restore/delete) that stops click propagation.", + "tags": ["ui", "component"] + }, + { + "id": "file:src/components/FilterBar.vue", + "type": "file", + "name": "components/FilterBar.vue", + "filePath": "src/components/FilterBar.vue", + "summary": "Filter toolbar for the applications list. Provides dropdowns for status, company category, job source, and minimum skills match. Sort by field/direction controls, include-archived checkbox, active filter count badge, and clear-filters button. Emits update:filters (partial FilterState) and clearFilters events.", + "tags": ["ui", "component"] + }, + { + "id": "file:src/components/HistoryPanel.vue", + "type": "file", + "name": "components/HistoryPanel.vue", + "filePath": "src/components/HistoryPanel.vue", + "summary": "Fixed slide-in panel (right side) showing the chronological commit log for an application. Displays each commit with relative timestamp, highlights the current version, and allows expanding to see EventDiff. Provides 'Restore to this point' button (calls eventService.restore for server-side events). Loads history from historyStore on mount.", + "tags": ["ui", "component", "event-sourcing"] + }, + { + "id": "file:src/components/UndoRedoBar.vue", + "type": "file", + "name": "components/UndoRedoBar.vue", + "filePath": "src/components/UndoRedoBar.vue", + "summary": "Compact toolbar with undo and redo buttons. Reads canUndo/canRedo from historyStore. Shows the current commit description as a label. Triggers historyStore.undo/redo on click.", + "tags": ["ui", "component", "event-sourcing"] + }, + { + "id": "file:src/components/EventDiff.vue", + "type": "file", + "name": "components/EventDiff.vue", + "filePath": "src/components/EventDiff.vue", + "summary": "Presentational component rendering a FieldChange[] diff. Shows each changed field's label, old value (struck-through red), and new value (green). Handles null/undefined values gracefully.", + "tags": ["ui", "component", "event-sourcing"] + }, + { + "id": "file:src/components/StatusBadge.vue", + "type": "file", + "name": "components/StatusBadge.vue", + "filePath": "src/components/StatusBadge.vue", + "summary": "Pill badge rendering an ApplicationStatus with color-coded styling. Supports 'small' and 'medium' sizes. Maps all 8 statuses (unsubmitted, applied, interviewing, given offer, accepted offer, rejected, declined offer, no offer) to color classes.", + "tags": ["ui", "component"] + }, + { + "id": "file:src/components/RatingInput.vue", + "type": "file", + "name": "components/RatingInput.vue", + "filePath": "src/components/RatingInput.vue", + "summary": "Accessible 5-star rating input using v-model. Supports hover preview, click-to-clear (allowClear prop), keyboard navigation (Space/Enter), and aria-checked attributes. Used for skills match and performance rating fields.", + "tags": ["ui", "component"] + }, + { + "id": "file:src/components/RatingDisplay.vue", + "type": "file", + "name": "components/RatingDisplay.vue", + "filePath": "src/components/RatingDisplay.vue", + "summary": "Read-only star rating display. Renders filled/outline stars up to configurable max (default 5). Optional numeric label. Used in ApplicationCard and InterviewStageItem.", + "tags": ["ui", "component"] + }, + { + "id": "file:src/components/ConfirmDialog.vue", + "type": "file", + "name": "components/ConfirmDialog.vue", + "filePath": "src/components/ConfirmDialog.vue", + "summary": "Modal confirmation dialog using Vue Teleport (renders to body). Supports destructive styling (red warning icon). Closes on Escape key, backdrop click, or cancel button. Prevents body scroll while open. Emits confirm and cancel.", + "tags": ["ui", "component"] + }, + { + "id": "file:src/components/EmptyState.vue", + "type": "file", + "name": "components/EmptyState.vue", + "filePath": "src/components/EmptyState.vue", + "summary": "Centered empty state display with icon slot, title, description, and optional action slot. Used when the applications list is empty.", + "tags": ["ui", "component"] + }, + { + "id": "file:src/components/InterviewStageForm.vue", + "type": "file", + "name": "components/InterviewStageForm.vue", + "filePath": "src/components/InterviewStageForm.vue", + "summary": "Inline form for creating or editing an interview stage. Fields: name, order, isCompleted checkbox, completedDate (conditional), performance rating (RatingInput), and notes. Validates name (required, max 100 chars) and order (non-negative). Emits save with CreateInterviewStageInput | UpdateInterviewStageInput.", + "tags": ["ui", "component"] + }, + { + "id": "file:src/components/InterviewStageItem.vue", + "type": "file", + "name": "components/InterviewStageItem.vue", + "filePath": "src/components/InterviewStageItem.vue", + "summary": "Read-only row for an interview stage in the list. Shows completion toggle (CheckCircle/ring), stage name, completed date, RatingDisplay for performance rating, and notes indicator. Expands inline to show notes on click. Edit and delete buttons emit events.", + "tags": ["ui", "component"] + }, + { + "id": "file:src/components/Pagination.vue", + "type": "file", + "name": "components/Pagination.vue", + "filePath": "src/components/Pagination.vue", + "summary": "Pagination nav with smart page window: shows all pages up to 7, otherwise shows first/last with ellipsis and pages around current. Previous/next buttons. Emits pageChange with page number.", + "tags": ["ui", "component"] + }, + { + "id": "file:src/components/UrlFieldInput.vue", + "type": "file", + "name": "components/UrlFieldInput.vue", + "filePath": "src/components/UrlFieldInput.vue", + "summary": "URL input field with an inline 'open in new tab' button that appears when the value starts with http:// or https://. Supports label, placeholder, and error display. Used for company URL, career page URL, and job posting URL fields.", + "tags": ["ui", "component"] + }, + { + "id": "file:src/style.css", + "type": "file", + "name": "style.css", + "filePath": "src/style.css", + "summary": "Global CSS entry point importing Tailwind CSS. Defines component layer utilities (.btn, .btn-primary, .btn-secondary, .btn-danger, .card, .input, .label) used throughout the component tree.", + "tags": ["config", "ui"] + }, + { + "id": "file:src/vite-env.d.ts", + "type": "file", + "name": "vite-env.d.ts", + "filePath": "src/vite-env.d.ts", + "summary": "Vite client type declaration file. References vite/client to enable import.meta.env types and asset module declarations.", + "tags": ["config", "type-definition"] + }, + { + "id": "file:src/shims-vue-router.d.ts", + "type": "file", + "name": "shims-vue-router.d.ts", + "filePath": "src/shims-vue-router.d.ts", + "summary": "Vue Router module augmentation to add custom meta fields to route definitions.", + "tags": ["config", "type-definition"] + }, + { + "id": "file:vite.config.ts", + "type": "file", + "name": "vite.config.ts", + "filePath": "vite.config.ts", + "summary": "Vite configuration. Registers @vitejs/plugin-vue. Configures path aliases: @ → src/, @shared → ../nuxt-api/shared/. Dev server on port 3020 with /api proxy to nuxt-api on port 5040. Vitest setup: jsdom environment, globals true, excludes e2e tests.", + "tags": ["config"] + }, + { + "id": "file:tsconfig.json", + "type": "file", + "name": "tsconfig.json", + "filePath": "tsconfig.json", + "summary": "TypeScript project configuration. References tsconfig.node.json for build tools. Extends @vue/tsconfig/tsconfig.dom.json for Vue/DOM types. Enables strict mode and paths aliases (@/*, @shared/*) matching vite.config.ts.", + "tags": ["config"] + }, + { + "id": "file:src/components/RatingInput.test.ts", + "type": "file", + "name": "RatingInput.test.ts", + "filePath": "src/components/RatingInput.test.ts", + "summary": "Unit tests for RatingInput component using @testing-library/vue and Vitest.", + "tags": ["test"] + }, + { + "id": "file:src/stores/__tests__/applicationDetail.test.ts", + "type": "file", + "name": "stores/__tests__/applicationDetail.test.ts", + "filePath": "src/stores/__tests__/applicationDetail.test.ts", + "summary": "Unit tests for applicationDetail Pinia store: verifies fetch, update, delete, archive, restore, and interview stage CRUD with mocked API services.", + "tags": ["test"] + }, + { + "id": "file:src/stores/__tests__/history.test.ts", + "type": "file", + "name": "stores/__tests__/history.test.ts", + "filePath": "src/stores/__tests__/history.test.ts", + "summary": "Unit tests for history Pinia store: verifies addCommit, undo/redo cursor movement, canUndo/canRedo, loadHistory from server events, and clearHistory.", + "tags": ["test"] + }, + { + "id": "file:src/utils/__tests__/eventDescriptions.test.ts", + "type": "file", + "name": "utils/__tests__/eventDescriptions.test.ts", + "filePath": "src/utils/__tests__/eventDescriptions.test.ts", + "summary": "Unit tests for generateFieldChanges and generateDescription utility functions.", + "tags": ["test"] + } + ], + "edges": [ + { "source": "file:src/main.ts", "target": "file:src/App.vue", "type": "imports", "label": "root component" }, + { "source": "file:src/main.ts", "target": "file:src/style.css", "type": "imports", "label": "global styles" }, + { "source": "file:src/main.ts", "target": "file:src/views/ApplicationList.vue", "type": "configures", "label": "route /" }, + { "source": "file:src/main.ts", "target": "file:src/views/ApplicationEdit.vue", "type": "configures", "label": "routes /applications/new and /applications/:id" }, + + { "source": "file:src/App.vue", "target": "file:src/composables/useDarkMode.ts", "type": "depends_on", "label": "useDarkMode()" }, + + { "source": "file:src/services/api.ts", "target": "file:src/types/index.ts", "type": "imports", "label": "domain types" }, + + { "source": "file:src/stores/applicationsList.ts", "target": "file:src/services/api.ts", "type": "depends_on", "label": "applicationService, eventService" }, + { "source": "file:src/stores/applicationsList.ts", "target": "file:src/types/index.ts", "type": "imports", "label": "Application, FilterState, PaginatedResponse" }, + { "source": "file:src/stores/applicationsList.ts", "target": "file:src/utils/eventDescriptions.ts", "type": "depends_on", "label": "generateDescription" }, + + { "source": "file:src/stores/applicationDetail.ts", "target": "file:src/services/api.ts", "type": "depends_on", "label": "applicationService, interviewStageService, eventService" }, + { "source": "file:src/stores/applicationDetail.ts", "target": "file:src/types/index.ts", "type": "imports", "label": "Application, UpdateApplicationInput, etc." }, + { "source": "file:src/stores/applicationDetail.ts", "target": "file:src/utils/eventDescriptions.ts", "type": "depends_on", "label": "generateFieldChanges, generateDescription" }, + { "source": "file:src/stores/applicationDetail.ts", "target": "file:src/stores/history.ts", "type": "depends_on", "label": "useHistoryStore().addCommit" }, + + { "source": "file:src/stores/history.ts", "target": "file:src/services/api.ts", "type": "depends_on", "label": "applicationService, interviewStageService, eventService" }, + { "source": "file:src/stores/history.ts", "target": "file:src/types/index.ts", "type": "imports", "label": "Application, InterviewStage, ImmerPatch, etc." }, + { "source": "file:src/stores/history.ts", "target": "file:src/stores/applicationDetail.ts", "type": "depends_on", "label": "useApplicationDetailStore().application, isUndoRedoInProgress" }, + + { "source": "file:src/utils/eventDescriptions.ts", "target": "file:src/types/index.ts", "type": "imports", "label": "Application, FieldChange, APPLICATION_STATUSES" }, + + { "source": "file:src/views/ApplicationList.vue", "target": "file:src/stores/applicationsList.ts", "type": "depends_on", "label": "useApplicationsListStore" }, + { "source": "file:src/views/ApplicationList.vue", "target": "file:src/components/ApplicationCard.vue", "type": "contains", "label": "v-for loop" }, + { "source": "file:src/views/ApplicationList.vue", "target": "file:src/components/FilterBar.vue", "type": "contains", "label": "filter bar" }, + { "source": "file:src/views/ApplicationList.vue", "target": "file:src/components/Pagination.vue", "type": "contains", "label": "pagination" }, + { "source": "file:src/views/ApplicationList.vue", "target": "file:src/components/EmptyState.vue", "type": "contains", "label": "empty state" }, + { "source": "file:src/views/ApplicationList.vue", "target": "file:src/components/ConfirmDialog.vue", "type": "contains", "label": "delete confirm dialog" }, + + { "source": "file:src/views/ApplicationEdit.vue", "target": "file:src/stores/applicationDetail.ts", "type": "depends_on", "label": "useApplicationDetailStore" }, + { "source": "file:src/views/ApplicationEdit.vue", "target": "file:src/stores/applicationsList.ts", "type": "depends_on", "label": "useApplicationsListStore (create mode)" }, + { "source": "file:src/views/ApplicationEdit.vue", "target": "file:src/stores/history.ts", "type": "depends_on", "label": "useHistoryStore (undo/redo/load)" }, + { "source": "file:src/views/ApplicationEdit.vue", "target": "file:src/types/index.ts", "type": "imports", "label": "types and constants" }, + { "source": "file:src/views/ApplicationEdit.vue", "target": "file:src/components/RatingInput.vue", "type": "contains", "label": "skills match" }, + { "source": "file:src/views/ApplicationEdit.vue", "target": "file:src/components/UrlFieldInput.vue", "type": "contains", "label": "URL fields x3" }, + { "source": "file:src/views/ApplicationEdit.vue", "target": "file:src/components/InterviewStageForm.vue", "type": "contains", "label": "add/edit stage forms" }, + { "source": "file:src/views/ApplicationEdit.vue", "target": "file:src/components/InterviewStageItem.vue", "type": "contains", "label": "stage list items" }, + { "source": "file:src/views/ApplicationEdit.vue", "target": "file:src/components/ConfirmDialog.vue", "type": "contains", "label": "discard/delete/stage-delete dialogs" }, + { "source": "file:src/views/ApplicationEdit.vue", "target": "file:src/components/UndoRedoBar.vue", "type": "contains", "label": "undo/redo toolbar" }, + { "source": "file:src/views/ApplicationEdit.vue", "target": "file:src/components/HistoryPanel.vue", "type": "contains", "label": "history slide-in panel" }, + + { "source": "file:src/components/ApplicationCard.vue", "target": "file:src/components/StatusBadge.vue", "type": "contains", "label": "status badge" }, + { "source": "file:src/components/ApplicationCard.vue", "target": "file:src/components/RatingDisplay.vue", "type": "contains", "label": "skills match display" }, + { "source": "file:src/components/ApplicationCard.vue", "target": "file:src/types/index.ts", "type": "imports", "label": "Application, COMPANY_CATEGORIES" }, + + { "source": "file:src/components/FilterBar.vue", "target": "file:src/types/index.ts", "type": "imports", "label": "FilterState, APPLICATION_STATUSES, etc." }, + + { "source": "file:src/components/HistoryPanel.vue", "target": "file:src/stores/history.ts", "type": "depends_on", "label": "useHistoryStore" }, + { "source": "file:src/components/HistoryPanel.vue", "target": "file:src/components/EventDiff.vue", "type": "contains", "label": "change diff display" }, + { "source": "file:src/components/HistoryPanel.vue", "target": "file:src/services/api.ts", "type": "depends_on", "label": "eventService.restore (dynamic import)" }, + { "source": "file:src/components/HistoryPanel.vue", "target": "file:src/stores/applicationDetail.ts", "type": "depends_on", "label": "useApplicationDetailStore (dynamic import)" }, + + { "source": "file:src/components/UndoRedoBar.vue", "target": "file:src/stores/history.ts", "type": "depends_on", "label": "useHistoryStore" }, + + { "source": "file:src/components/InterviewStageForm.vue", "target": "file:src/components/RatingInput.vue", "type": "contains", "label": "performance rating" }, + { "source": "file:src/components/InterviewStageForm.vue", "target": "file:src/types/index.ts", "type": "imports", "label": "InterviewStage, CreateInterviewStageInput, etc." }, + + { "source": "file:src/components/InterviewStageItem.vue", "target": "file:src/components/RatingDisplay.vue", "type": "contains", "label": "performance rating display" }, + { "source": "file:src/components/InterviewStageItem.vue", "target": "file:src/types/index.ts", "type": "imports", "label": "InterviewStage" }, + + { "source": "file:src/components/StatusBadge.vue", "target": "file:src/types/index.ts", "type": "imports", "label": "ApplicationStatus" }, + { "source": "file:src/components/EventDiff.vue", "target": "file:src/types/index.ts", "type": "imports", "label": "FieldChange" }, + + { "source": "file:vite.config.ts", "target": "file:src/main.ts", "type": "configures", "label": "app entry point" }, + { "source": "file:vite.config.ts", "target": "file:src/vite-env.d.ts", "type": "configures", "label": "client type declarations" }, + { "source": "file:tsconfig.json", "target": "file:src/shims-vue-router.d.ts", "type": "configures", "label": "module augmentation" }, + { "source": "file:tsconfig.json", "target": "file:vite.config.ts", "type": "configures", "label": "tsconfig references vite config paths" }, + + { "source": "file:src/stores/__tests__/applicationDetail.test.ts", "target": "file:src/stores/applicationDetail.ts", "type": "tests", "label": "unit tests" }, + { "source": "file:src/stores/__tests__/history.test.ts", "target": "file:src/stores/history.ts", "type": "tests", "label": "unit tests" }, + { "source": "file:src/utils/__tests__/eventDescriptions.test.ts", "target": "file:src/utils/eventDescriptions.ts", "type": "tests", "label": "unit tests" }, + { "source": "file:src/components/RatingInput.test.ts", "target": "file:src/components/RatingInput.vue", "type": "tests", "label": "component tests" } + ], + "layers": [ + { + "id": "layer:config", + "name": "Config Layer", + "description": "Application bootstrap, plugin registration, routing, build configuration, and type declaration files.", + "nodeIds": [ + "file:src/main.ts", + "file:src/App.vue", + "file:src/style.css", + "file:vite.config.ts", + "file:tsconfig.json", + "file:src/vite-env.d.ts", + "file:src/shims-vue-router.d.ts" + ] + }, + { + "id": "layer:service", + "name": "Service Layer", + "description": "Pinia stores for application state (list, detail, history), HTTP API client, and the dark-mode composable. This layer encapsulates all data-fetching, mutation, and event-sourcing logic.", + "nodeIds": [ + "file:src/services/api.ts", + "file:src/stores/applicationsList.ts", + "file:src/stores/applicationDetail.ts", + "file:src/stores/history.ts", + "file:src/composables/useDarkMode.ts" + ] + }, + { + "id": "layer:ui", + "name": "UI Layer", + "description": "Vue SFC views (page-level) and reusable components. Views map to router routes; components are composed within views and each other.", + "nodeIds": [ + "file:src/views/ApplicationList.vue", + "file:src/views/ApplicationEdit.vue", + "file:src/components/ApplicationCard.vue", + "file:src/components/FilterBar.vue", + "file:src/components/HistoryPanel.vue", + "file:src/components/UndoRedoBar.vue", + "file:src/components/EventDiff.vue", + "file:src/components/StatusBadge.vue", + "file:src/components/RatingInput.vue", + "file:src/components/RatingDisplay.vue", + "file:src/components/ConfirmDialog.vue", + "file:src/components/EmptyState.vue", + "file:src/components/InterviewStageForm.vue", + "file:src/components/InterviewStageItem.vue", + "file:src/components/Pagination.vue", + "file:src/components/UrlFieldInput.vue" + ] + }, + { + "id": "layer:utility", + "name": "Utility Layer", + "description": "Pure utility functions and type definitions shared across the app.", + "nodeIds": [ + "file:src/utils/eventDescriptions.ts", + "file:src/types/index.ts" + ] + }, + { + "id": "layer:test", + "name": "Test Layer", + "description": "Unit and component tests using Vitest and @testing-library/vue.", + "nodeIds": [ + "file:src/components/RatingInput.test.ts", + "file:src/stores/__tests__/applicationDetail.test.ts", + "file:src/stores/__tests__/history.test.ts", + "file:src/utils/__tests__/eventDescriptions.test.ts" + ] + } + ], + "tour": [ + { + "order": 1, + "title": "Application Bootstrap", + "description": "Start at main.ts, which wires the whole app together: creates a Vue app, installs Pinia (state) and Vue Router (navigation), calls enablePatches() to turn on Immer patch tracking for undo/redo, and mounts to #app. The three routes — / (list), /applications/new, and /applications/:id — map to the two view components.", + "nodeIds": ["file:src/main.ts"] + }, + { + "order": 2, + "title": "Domain Types (from nuxt-api shared)", + "description": "src/types/index.ts is a pure re-export barrel from @shared/types (the nuxt-api package). All domain types live there — Application, InterviewStage, ApplicationStatus, etc. — and event-sourcing types (ImmerPatch, FieldChange, ApplicationEvent, ApplicationSnapshot). Understanding these types is prerequisite to reading any other file.", + "nodeIds": ["file:src/types/index.ts"] + }, + { + "order": 3, + "title": "HTTP API Client", + "description": "src/services/api.ts wraps all backend calls. It exports three service objects (applicationService, interviewStageService, eventService) and a checkHealth function. All requests go to /api which is proxied to the nuxt-api server on port 5040. It handles 204 No Content responses explicitly.", + "nodeIds": ["file:src/services/api.ts"] + }, + { + "order": 4, + "title": "Applications List Store", + "description": "src/stores/applicationsList.ts is the Pinia store for the list page. It manages paginated data, filter state, and all CRUD operations visible from the list. A deep watch on filters auto-triggers fetchApplications() — so changing any filter immediately refreshes the list.", + "nodeIds": ["file:src/stores/applicationsList.ts"] + }, + { + "order": 5, + "title": "Application Detail Store + Immer Event Sourcing", + "description": "src/stores/applicationDetail.ts is the heart of the edit flow. Every mutation uses Immer's produceWithPatches to capture before/after diffs as patch arrays. It then calls eventService.append to persist the event to the server and delegates to historyStore.addCommit to update the local undo stack. The isUndoRedoInProgress flag prevents double-recording during undo/redo replays.", + "nodeIds": ["file:src/stores/applicationDetail.ts"] + }, + { + "order": 6, + "title": "History / Undo-Redo Store", + "description": "src/stores/history.ts implements per-application undo/redo using a cursor into a commit array. undo() applies inversePatches to roll back state and calls syncStateToServer() to push the rolled-back state to the API. redo() applies forward patches. loadHistory() seeds the local commit log from server events, enabling history to survive page reloads.", + "nodeIds": ["file:src/stores/history.ts"] + }, + { + "order": 7, + "title": "Event Description Utilities", + "description": "src/utils/eventDescriptions.ts provides two pure functions: generateFieldChanges (compares old/new Application and returns FieldChange[] for a given key list) and generateDescription (produces human-readable event labels like 'Changed status to Interviewing'). These are used by both applicationDetail and applicationsList stores.", + "nodeIds": ["file:src/utils/eventDescriptions.ts"] + }, + { + "order": 8, + "title": "Application List View", + "description": "src/views/ApplicationList.vue is the route / view. It composes FilterBar, ApplicationCard (in a v-for), Pagination, EmptyState, and a delete ConfirmDialog. It delegates all data and actions to useApplicationsListStore, keeping the view thin.", + "nodeIds": ["file:src/views/ApplicationList.vue", "file:src/components/FilterBar.vue", "file:src/components/ApplicationCard.vue", "file:src/components/Pagination.vue"] + }, + { + "order": 9, + "title": "Application Edit View (Create + Edit)", + "description": "src/views/ApplicationEdit.vue handles both create and edit modes via the isEditMode computed (derived from props.id). It uses snapshot-based dirty tracking, a skipNavGuard ref to bypass the unsaved-changes guard after intentional navigation, and keyboard shortcuts (Ctrl+Z/Y) for undo/redo. In edit mode it shows UndoRedoBar and HistoryPanel.", + "nodeIds": ["file:src/views/ApplicationEdit.vue"] + }, + { + "order": 10, + "title": "Event-Sourcing UI Components", + "description": "UndoRedoBar shows undo/redo buttons and the current action description. HistoryPanel is a slide-in panel listing all commits with relative timestamps; expanding a commit shows EventDiff (red strikethrough old value, green new value). HistoryPanel also supports server-side point-in-time restore via eventService.restore.", + "nodeIds": ["file:src/components/UndoRedoBar.vue", "file:src/components/HistoryPanel.vue", "file:src/components/EventDiff.vue"] + }, + { + "order": 11, + "title": "Dark Mode Composable", + "description": "src/composables/useDarkMode.ts is the one composable in the project. It manages localStorage persistence, system prefers-color-scheme fallback, and live media-query updates. It's consumed only by App.vue for the header toggle button.", + "nodeIds": ["file:src/composables/useDarkMode.ts", "file:src/App.vue"] + }, + { + "order": 12, + "title": "Shared Presentational Components", + "description": "StatusBadge maps ApplicationStatus values to color-coded pills. RatingInput is an accessible 5-star picker (v-model, keyboard nav). RatingDisplay is its read-only counterpart. ConfirmDialog is a teleported modal. EmptyState, UrlFieldInput, InterviewStageForm, InterviewStageItem, and Pagination round out the component library.", + "nodeIds": [ + "file:src/components/StatusBadge.vue", + "file:src/components/RatingInput.vue", + "file:src/components/RatingDisplay.vue", + "file:src/components/ConfirmDialog.vue", + "file:src/components/EmptyState.vue", + "file:src/components/UrlFieldInput.vue", + "file:src/components/InterviewStageForm.vue", + "file:src/components/InterviewStageItem.vue" + ] + } + ] +} diff --git a/vue-ui/.understand-anything/meta.json b/vue-ui/.understand-anything/meta.json new file mode 100644 index 00000000..aeccf0cb --- /dev/null +++ b/vue-ui/.understand-anything/meta.json @@ -0,0 +1,41 @@ +{ + "lastAnalyzedAt": "2026-05-04T00:00:00.000Z", + "gitCommitHash": "e5054096efec8165e7d4cfe11fc103f77ad2d98f", + "version": "1.0.0", + "analyzedFiles": [ + "src/main.ts", + "src/App.vue", + "src/types/index.ts", + "src/services/api.ts", + "src/stores/applicationsList.ts", + "src/stores/applicationDetail.ts", + "src/stores/history.ts", + "src/composables/useDarkMode.ts", + "src/utils/eventDescriptions.ts", + "src/views/ApplicationList.vue", + "src/views/ApplicationEdit.vue", + "src/components/ApplicationCard.vue", + "src/components/FilterBar.vue", + "src/components/HistoryPanel.vue", + "src/components/UndoRedoBar.vue", + "src/components/EventDiff.vue", + "src/components/StatusBadge.vue", + "src/components/RatingInput.vue", + "src/components/RatingDisplay.vue", + "src/components/ConfirmDialog.vue", + "src/components/EmptyState.vue", + "src/components/InterviewStageForm.vue", + "src/components/InterviewStageItem.vue", + "src/components/Pagination.vue", + "src/components/UrlFieldInput.vue", + "src/style.css", + "src/vite-env.d.ts", + "src/shims-vue-router.d.ts", + "vite.config.ts", + "tsconfig.json", + "src/components/RatingInput.test.ts", + "src/stores/__tests__/applicationDetail.test.ts", + "src/stores/__tests__/history.test.ts", + "src/utils/__tests__/eventDescriptions.test.ts" + ] +} diff --git a/yoga-api/.understand-anything/.understandignore b/yoga-api/.understand-anything/.understandignore new file mode 100644 index 00000000..6a9547e7 --- /dev/null +++ b/yoga-api/.understand-anything/.understandignore @@ -0,0 +1,4 @@ +# .understandignore — patterns for files/dirs to exclude from analysis +# Built-in defaults (always excluded): node_modules/, .git/, dist/, build/, *.lock + +# Exclude generated Prisma client (in node_modules — already excluded by default) diff --git a/yoga-api/.understand-anything/knowledge-graph.json b/yoga-api/.understand-anything/knowledge-graph.json new file mode 100644 index 00000000..a032e5a7 --- /dev/null +++ b/yoga-api/.understand-anything/knowledge-graph.json @@ -0,0 +1,368 @@ +{ + "nodes": [ + { + "id": "file:src/index.ts", + "type": "service", + "name": "index.ts", + "filePath": "src/index.ts", + "summary": "Main server entry point. Creates a Node.js HTTP server that dual-routes traffic: REST API endpoints for /api/applications (CRUD, CSV import/export, stage management) and a GraphQL Yoga endpoint at /graphql. Implements CSV parsing, multipart file upload handling via Busboy, and status/enum mapping between display names and Prisma enum identifiers. Listens on PORT env var (default 5080).", + "tags": ["server", "entry-point", "rest", "graphql", "csv", "yoga", "busboy"], + "complexity": "high" + }, + { + "id": "file:src/db/client.ts", + "type": "service", + "name": "client.ts", + "filePath": "src/db/client.ts", + "summary": "Singleton Prisma client factory. Extracts the schema parameter from DATABASE_URL, removes it before creating a PrismaPg adapter (pg driver), then instantiates PrismaClient with the adapter. Uses globalThis singleton pattern to prevent multiple client instances in dev hot-reload.", + "tags": ["database", "prisma", "postgresql", "singleton", "pg-adapter"], + "complexity": "low" + }, + { + "id": "file:src/schema/builder.ts", + "type": "config", + "name": "builder.ts", + "filePath": "src/schema/builder.ts", + "summary": "Pothos SchemaBuilder configuration. Instantiates the Pothos builder with PrismaPlugin, wiring in the Prisma client and generated DMMF (data model meta-format) from pothos-types.ts. Defines the Context type as an empty record.", + "tags": ["graphql", "pothos", "schema-builder", "prisma-plugin"], + "complexity": "low" + }, + { + "id": "file:src/schema/pothos-types.ts", + "type": "config", + "name": "pothos-types.ts", + "filePath": "src/schema/pothos-types.ts", + "summary": "Auto-generated Pothos type definitions produced by the prisma-pothos-types generator. Exports TypeScript types that map Prisma models (Application, InterviewStage, ApplicationHistory) to Pothos object types, plus the getDatamodel() function for DMMF access.", + "tags": ["generated", "pothos", "prisma", "types", "dmmf"], + "complexity": "moderate" + }, + { + "id": "file:src/schema/enums.ts", + "type": "file", + "name": "enums.ts", + "filePath": "src/schema/enums.ts", + "summary": "Registers Prisma enums as Pothos/GraphQL enum types. Exports ApplicationStatusEnum, CompanyCategoryEnum, and JobSourceEnum — bridging Prisma's TypeScript enums into the GraphQL schema.", + "tags": ["graphql", "enums", "pothos", "prisma"], + "complexity": "low" + }, + { + "id": "file:src/schema/types.ts", + "type": "file", + "name": "types.ts", + "filePath": "src/schema/types.ts", + "summary": "Defines Pothos GraphQL object types for the schema: ApplicationType (full application with relations), InterviewStageType, HistoryEntryType, ApplicationListRef (paginated list), and HistoryListRef. Handles date serialization from DateTime to YYYY-MM-DD strings.", + "tags": ["graphql", "pothos", "types", "object-types", "pagination"], + "complexity": "moderate" + }, + { + "id": "file:src/schema/queries.ts", + "type": "file", + "name": "queries.ts", + "filePath": "src/schema/queries.ts", + "summary": "Defines GraphQL Query fields: 'applications' (paginated list with filters for status, companyCategory, jobSource, skillsMatchMin, sortBy, sortDir, includeArchived), 'application' (single by ID), 'applicationHistory' (paginated history for an application). Delegates to service layer.", + "tags": ["graphql", "queries", "pothos", "pagination", "filtering"], + "complexity": "moderate" + }, + { + "id": "file:src/schema/mutations.ts", + "type": "file", + "name": "mutations.ts", + "filePath": "src/schema/mutations.ts", + "summary": "Defines GraphQL Mutation fields: createApplication, updateApplication, deleteApplication, archiveApplication, restoreApplication, createStage, updateStage, deleteStage, restoreHistory. Defines input types (CreateApplicationInput, UpdateApplicationInput, StageInput). Delegates to service layer.", + "tags": ["graphql", "mutations", "pothos", "input-types", "crud"], + "complexity": "moderate" + }, + { + "id": "file:src/schema/index.ts", + "type": "file", + "name": "index.ts (schema)", + "filePath": "src/schema/index.ts", + "summary": "Schema assembly module. Imports all schema modules to trigger Pothos registration side effects (enums, types, queries, mutations), then calls builder.queryType({}), builder.mutationType({}), and exports the built schema via builder.toSchema().", + "tags": ["graphql", "schema", "pothos", "assembly"], + "complexity": "low" + }, + { + "id": "file:src/services/application.service.ts", + "type": "service", + "name": "application.service.ts", + "filePath": "src/services/application.service.ts", + "summary": "Core business logic for job applications. Implements listApplications (with filters, sorting, pagination), getApplication, getAllApplications, createApplication (with dateApplied auto-set logic), updateApplication (with terminal status guard, dateApplied transitions), deleteApplication, archiveApplication, and restoreApplication. Every mutating operation records a history snapshot via history.service. Input validation enforces non-empty names, skillsMatch 1-5, non-negative salaries.", + "tags": ["service", "business-logic", "crud", "pagination", "validation", "transactions"], + "complexity": "high" + }, + { + "id": "file:src/services/stages.service.ts", + "type": "service", + "name": "stages.service.ts", + "filePath": "src/services/stages.service.ts", + "summary": "Business logic for interview stages. Implements createStage (validates name, order, checks application exists), updateStage (partial update), and deleteStage (with post-delete history snapshot). Validates that stage name is non-empty and order is a non-negative integer.", + "tags": ["service", "interview-stages", "crud", "validation"], + "complexity": "moderate" + }, + { + "id": "file:src/services/history.service.ts", + "type": "service", + "name": "history.service.ts", + "filePath": "src/services/history.service.ts", + "summary": "Audit history service for application changes. recordSnapshot captures a full application snapshot with auto-incremented sequence number and list of changed fields — called inside transactions by other services. listHistory returns paginated history entries for an application. restoreToSnapshot rolls an application back to a previous snapshot state and records the restore as a new history entry.", + "tags": ["service", "history", "audit", "snapshots", "transactions", "pagination"], + "complexity": "moderate" + }, + { + "id": "file:src/__tests__/application.service.test.ts", + "type": "file", + "name": "application.service.test.ts", + "filePath": "src/__tests__/application.service.test.ts", + "summary": "Unit tests for application.service.ts using Vitest and vitest-mock-extended. Tests cover: listApplications excludes archived by default, createApplication validates required fields and skillsMatch range, auto-sets dateApplied for submitted statuses, updateApplication blocks terminal status transitions and clears dateApplied on unsubmitted, deleteApplication returns true. Uses deep mock of PrismaClient.", + "tags": ["test", "unit-test", "vitest", "mock", "application-service"], + "complexity": "moderate" + }, + { + "id": "file:src/__tests__/stages.service.test.ts", + "type": "file", + "name": "stages.service.test.ts", + "filePath": "src/__tests__/stages.service.test.ts", + "summary": "Unit tests for stages.service.ts using Vitest and vitest-mock-extended. Tests cover: createStage validates empty/whitespace name and negative order, deleteStage returns true on success. Uses deep mock of PrismaClient.", + "tags": ["test", "unit-test", "vitest", "mock", "stages-service"], + "complexity": "low" + }, + { + "id": "file:prisma/schema.prisma", + "type": "schema", + "name": "schema.prisma", + "filePath": "prisma/schema.prisma", + "summary": "Prisma ORM schema for the graphql_yoga PostgreSQL schema. Defines three models: Application (core job application record with 20+ fields, enum columns for status/category/source), InterviewStage (ordered interview steps with completion tracking), and ApplicationHistory (immutable audit log with JSON snapshot and changedFields). Defines three enums: ApplicationStatus (8 values), CompanyCategory (19 values), JobSource (7 values).", + "tags": ["schema", "prisma", "postgresql", "data-model", "enums", "relations"], + "complexity": "moderate" + }, + { + "id": "file:prisma/migrations/20260309172217_init/migration.sql", + "type": "file", + "name": "migration_init.sql", + "filePath": "prisma/migrations/20260309172217_init/migration.sql", + "summary": "Initial Prisma migration SQL. Creates the graphql_yoga schema enums (application_status, company_category, job_source) and tables (applications, interview_stages, application_history) with all columns, constraints, indexes, and foreign keys.", + "tags": ["migration", "sql", "postgresql", "schema-creation"], + "complexity": "moderate" + }, + { + "id": "file:prisma/migrations/20260309213719_fix_domain_model/migration.sql", + "type": "file", + "name": "migration_fix_domain.sql", + "filePath": "prisma/migrations/20260309213719_fix_domain_model/migration.sql", + "summary": "Second Prisma migration SQL fixing the domain model after initial creation.", + "tags": ["migration", "sql", "postgresql", "schema-update"], + "complexity": "low" + }, + { + "id": "file:package.json", + "type": "config", + "name": "package.json", + "filePath": "package.json", + "summary": "Node.js package manifest for yoga-api. Runtime deps: graphql-yoga 5.18.1, @pothos/core 4.12.0, @pothos/plugin-prisma 4.14.2, @prisma/client 7.6.0, @prisma/adapter-pg 7.6.0, busboy 1.6.0, zod 4.3.6, dotenv 16.5.0, pg 8.20.0. Dev deps include TypeScript 5.9.3, tsx, vitest 4.1.3. Scripts: dev (tsx watch), build (tsc), test (vitest run), lint, prisma commands.", + "tags": ["config", "npm", "dependencies", "scripts"], + "complexity": "low" + }, + { + "id": "file:tsconfig.json", + "type": "config", + "name": "tsconfig.json", + "filePath": "tsconfig.json", + "summary": "TypeScript compiler configuration. Target ES2022, module NodeNext (ESM), strict mode enabled, declaration output, rootDir src, outDir dist.", + "tags": ["config", "typescript", "esm", "strict"], + "complexity": "low" + }, + { + "id": "file:vitest.config.ts", + "type": "config", + "name": "vitest.config.ts", + "filePath": "vitest.config.ts", + "summary": "Vitest test runner configuration. Enables globals and sets node test environment.", + "tags": ["config", "testing", "vitest"], + "complexity": "low" + }, + { + "id": "file:eslint.config.js", + "type": "config", + "name": "eslint.config.js", + "filePath": "eslint.config.js", + "summary": "ESLint flat config for the project. Configures TypeScript-ESLint parser and plugin for src/ files.", + "tags": ["config", "linting", "eslint", "typescript"], + "complexity": "low" + }, + { + "id": "file:prisma.config.ts", + "type": "config", + "name": "prisma.config.ts", + "filePath": "prisma.config.ts", + "summary": "Prisma configuration file specifying the schema path and migration directory for Prisma CLI operations.", + "tags": ["config", "prisma", "migrations"], + "complexity": "low" + } + ], + "edges": [ + { "source": "file:src/index.ts", "target": "file:src/schema/index.ts", "type": "imports", "label": "imports schema" }, + { "source": "file:src/index.ts", "target": "file:src/db/client.ts", "type": "imports", "label": "imports prisma client" }, + { "source": "file:src/index.ts", "target": "file:src/services/application.service.ts", "type": "imports", "label": "imports application service" }, + { "source": "file:src/index.ts", "target": "file:src/services/stages.service.ts", "type": "imports", "label": "imports stages service" }, + { "source": "file:src/schema/builder.ts", "target": "file:src/schema/pothos-types.ts", "type": "imports", "label": "imports pothos types" }, + { "source": "file:src/schema/builder.ts", "target": "file:src/db/client.ts", "type": "imports", "label": "imports prisma singleton" }, + { "source": "file:src/schema/enums.ts", "target": "file:src/schema/builder.ts", "type": "imports", "label": "uses builder" }, + { "source": "file:src/schema/types.ts", "target": "file:src/schema/builder.ts", "type": "imports", "label": "uses builder" }, + { "source": "file:src/schema/types.ts", "target": "file:src/schema/enums.ts", "type": "imports", "label": "uses enum types" }, + { "source": "file:src/schema/queries.ts", "target": "file:src/schema/builder.ts", "type": "imports", "label": "uses builder" }, + { "source": "file:src/schema/queries.ts", "target": "file:src/schema/enums.ts", "type": "imports", "label": "uses enum args" }, + { "source": "file:src/schema/queries.ts", "target": "file:src/schema/types.ts", "type": "imports", "label": "uses object types" }, + { "source": "file:src/schema/queries.ts", "target": "file:src/services/application.service.ts", "type": "imports", "label": "calls service" }, + { "source": "file:src/schema/queries.ts", "target": "file:src/services/history.service.ts", "type": "imports", "label": "calls history service" }, + { "source": "file:src/schema/mutations.ts", "target": "file:src/schema/builder.ts", "type": "imports", "label": "uses builder" }, + { "source": "file:src/schema/mutations.ts", "target": "file:src/schema/enums.ts", "type": "imports", "label": "uses enum types" }, + { "source": "file:src/schema/mutations.ts", "target": "file:src/schema/types.ts", "type": "imports", "label": "uses object types" }, + { "source": "file:src/schema/mutations.ts", "target": "file:src/services/application.service.ts", "type": "imports", "label": "calls service" }, + { "source": "file:src/schema/mutations.ts", "target": "file:src/services/stages.service.ts", "type": "imports", "label": "calls stages service" }, + { "source": "file:src/schema/mutations.ts", "target": "file:src/services/history.service.ts", "type": "imports", "label": "calls history service" }, + { "source": "file:src/schema/index.ts", "target": "file:src/schema/builder.ts", "type": "imports", "label": "uses builder" }, + { "source": "file:src/schema/index.ts", "target": "file:src/schema/enums.ts", "type": "imports", "label": "side-effect import" }, + { "source": "file:src/schema/index.ts", "target": "file:src/schema/types.ts", "type": "imports", "label": "side-effect import" }, + { "source": "file:src/schema/index.ts", "target": "file:src/schema/queries.ts", "type": "imports", "label": "side-effect import" }, + { "source": "file:src/schema/index.ts", "target": "file:src/schema/mutations.ts", "type": "imports", "label": "side-effect import" }, + { "source": "file:src/services/application.service.ts", "target": "file:src/db/client.ts", "type": "imports", "label": "uses prisma client" }, + { "source": "file:src/services/application.service.ts", "target": "file:src/services/history.service.ts", "type": "imports", "label": "calls recordSnapshot" }, + { "source": "file:src/services/stages.service.ts", "target": "file:src/db/client.ts", "type": "imports", "label": "uses prisma client" }, + { "source": "file:src/services/stages.service.ts", "target": "file:src/services/history.service.ts", "type": "imports", "label": "calls recordSnapshot" }, + { "source": "file:src/services/history.service.ts", "target": "file:src/db/client.ts", "type": "imports", "label": "uses prisma client" }, + { "source": "file:src/__tests__/application.service.test.ts", "target": "file:src/db/client.ts", "type": "imports", "label": "mocks prisma client" }, + { "source": "file:src/__tests__/application.service.test.ts", "target": "file:src/services/application.service.ts", "type": "imports", "label": "tests" }, + { "source": "file:src/__tests__/stages.service.test.ts", "target": "file:src/db/client.ts", "type": "imports", "label": "mocks prisma client" }, + { "source": "file:src/__tests__/stages.service.test.ts", "target": "file:src/services/stages.service.ts", "type": "imports", "label": "tests" }, + { "source": "file:src/db/client.ts", "target": "file:prisma/schema.prisma", "type": "uses", "label": "driven by schema" }, + { "source": "file:src/schema/pothos-types.ts", "target": "file:prisma/schema.prisma", "type": "uses", "label": "generated from schema" } + ], + "layers": [ + { + "id": "layer:infrastructure", + "name": "Infrastructure & Configuration", + "description": "Database connectivity, build tooling configuration, and project scaffolding. The Prisma client singleton and all config files live here — they're the foundation every other layer depends on.", + "nodeIds": [ + "file:src/db/client.ts", + "file:prisma/schema.prisma", + "file:prisma/migrations/20260309172217_init/migration.sql", + "file:prisma/migrations/20260309213719_fix_domain_model/migration.sql", + "file:package.json", + "file:tsconfig.json", + "file:vitest.config.ts", + "file:eslint.config.js", + "file:prisma.config.ts" + ] + }, + { + "id": "layer:services", + "name": "Business Logic Services", + "description": "Pure service functions that encapsulate all domain logic: CRUD operations, validation, status-transition rules, history snapshot capture, and interview stage management. These have no direct HTTP or GraphQL dependencies — they accept typed inputs and return Prisma model objects.", + "nodeIds": [ + "file:src/services/application.service.ts", + "file:src/services/stages.service.ts", + "file:src/services/history.service.ts" + ] + }, + { + "id": "layer:graphql-schema", + "name": "GraphQL Schema Layer", + "description": "Pothos code-first schema definition. The builder wires the Prisma plugin; enums, object types, queries, and mutations register themselves with the builder via side-effect imports; the index module assembles the final executable schema. Generated pothos-types bridges the Prisma DMMF to TypeScript types used in resolvers.", + "nodeIds": [ + "file:src/schema/builder.ts", + "file:src/schema/pothos-types.ts", + "file:src/schema/enums.ts", + "file:src/schema/types.ts", + "file:src/schema/queries.ts", + "file:src/schema/mutations.ts", + "file:src/schema/index.ts" + ] + }, + { + "id": "layer:server", + "name": "HTTP Server & API Gateway", + "description": "The main server entry point composes the REST routing layer and the GraphQL Yoga handler on a single Node.js HTTP server. It handles CSV import/export (multipart via Busboy, full RFC-4180 CSV parsing), REST CRUD for /api/applications and /api/applications/:id/interview-stages, and proxies all other traffic to the Yoga GraphQL handler.", + "nodeIds": [ + "file:src/index.ts" + ] + }, + { + "id": "layer:tests", + "name": "Tests", + "description": "Vitest unit tests for business logic services, using vitest-mock-extended deep mocks of PrismaClient. Tests focus on validation edge cases, status transition guards, and auto-dateApplied logic without needing a real database.", + "nodeIds": [ + "file:src/__tests__/application.service.test.ts", + "file:src/__tests__/stages.service.test.ts" + ] + } + ], + "tour": [ + { + "id": "step:data-model", + "order": 1, + "title": "Data Model — The Prisma Schema", + "description": "Start here to understand what data yoga-api manages. prisma/schema.prisma defines three PostgreSQL models: Application (the core entity with 20+ fields including status, salary, and skill-match rating), InterviewStage (ordered steps within an application process), and ApplicationHistory (an immutable append-only audit log keyed by applicationId + sequence). Three enums — ApplicationStatus (8 values including terminal states accepted_offer/declined_offer), CompanyCategory (19 values), and JobSource (7 values) — use @map to store display-friendly values in the DB. All models live in the graphql_yoga schema namespace inside the shared app_tracker PostgreSQL database.", + "nodeIds": ["file:prisma/schema.prisma"] + }, + { + "id": "step:db-client", + "order": 2, + "title": "Database Connection — Prisma Client Singleton", + "description": "src/db/client.ts creates the single PrismaClient instance shared across the application. It parses DATABASE_URL to extract the schema query parameter (which Prisma's PrismaPg adapter uses to set the search_path), strips it from the connection string before passing it to the pg adapter, and wraps everything in a globalThis singleton to survive hot-reload in development.", + "nodeIds": ["file:src/db/client.ts"] + }, + { + "id": "step:application-service", + "order": 3, + "title": "Core Business Logic — Application Service", + "description": "src/services/application.service.ts is the heart of the API. It implements the full lifecycle of a job application: listApplications with filter/sort/pagination, getApplication, createApplication (auto-sets dateApplied when status is a submitted status, rejects empty names and out-of-range skillsMatch), updateApplication (guards against transitioning out of terminal statuses accepted_offer/declined_offer, manages dateApplied based on new status), deleteApplication, archiveApplication, and restoreApplication. Every mutating operation runs inside a Prisma transaction and calls recordSnapshot() at the end to write an audit trail entry.", + "nodeIds": ["file:src/services/application.service.ts"] + }, + { + "id": "step:history-service", + "order": 4, + "title": "Audit Trail — History Service", + "description": "src/services/history.service.ts provides the event-sourcing pattern for application changes. recordSnapshot() is called inside transactions by every mutating service — it auto-increments a per-application sequence number, captures a full JSON snapshot of the application, and records which fields changed. listHistory() returns paginated history entries sorted newest-first. restoreToSnapshot() rolls an application back to any previous snapshot by sequence number.", + "nodeIds": ["file:src/services/history.service.ts"] + }, + { + "id": "step:stages-service", + "order": 5, + "title": "Interview Stages — Stages Service", + "description": "src/services/stages.service.ts manages InterviewStage records (ordered steps like 'Phone Screen', 'Technical', 'Offer'). createStage validates the name is non-empty and order is a non-negative integer, verifies the parent application exists, then creates the stage and records a history snapshot on the application. updateStage supports partial updates. deleteStage removes the stage and also snapshots the updated application state.", + "nodeIds": ["file:src/services/stages.service.ts"] + }, + { + "id": "step:graphql-schema", + "order": 6, + "title": "GraphQL Schema — Pothos Code-First", + "description": "The schema/ directory uses Pothos (a code-first GraphQL schema builder) with the PrismaPlugin for automatic type inference from the Prisma DMMF. The flow: builder.ts creates the SchemaBuilder instance with PrismaPlugin configured; enums.ts registers the three Prisma enums as GraphQL enum types; types.ts defines ApplicationType, InterviewStageType, HistoryEntryType, and paginated list refs; queries.ts adds the Query root fields (applications, application, applicationHistory); mutations.ts adds all Mutation fields (CRUD for applications and stages, plus restoreHistory). Finally index.ts imports all modules to trigger registration side effects, then calls builder.toSchema() to produce the executable schema.", + "nodeIds": [ + "file:src/schema/builder.ts", + "file:src/schema/enums.ts", + "file:src/schema/types.ts", + "file:src/schema/queries.ts", + "file:src/schema/mutations.ts", + "file:src/schema/index.ts", + "file:src/schema/pothos-types.ts" + ] + }, + { + "id": "step:server", + "order": 7, + "title": "HTTP Server — Dual REST + GraphQL Gateway", + "description": "src/index.ts is the final composition layer. It creates a single Node.js HTTP server that routes requests before they ever reach GraphQL Yoga: GET /api/applications/export streams a CSV download, GET /api/applications/sample-csv returns a template CSV, POST /api/applications/import handles multipart file upload via Busboy with full RFC-4180 CSV parsing (including quoted fields with embedded newlines), GET/POST/PATCH /api/applications provide REST CRUD, and nested routes handle interview stages. Any request that doesn't match a REST route falls through to the GraphQL Yoga handler at /graphql.", + "nodeIds": ["file:src/index.ts"] + }, + { + "id": "step:tests", + "order": 8, + "title": "Testing — Vitest Unit Tests with Mocked Prisma", + "description": "Tests in src/__tests__/ use Vitest with vitest-mock-extended to create deep mocks of PrismaClient. application.service.test.ts covers archive exclusion, name validation, skillsMatch range validation, auto-dateApplied, and terminal status guard. stages.service.test.ts covers stage validation and delete success.", + "nodeIds": [ + "file:src/__tests__/application.service.test.ts", + "file:src/__tests__/stages.service.test.ts" + ] + } + ] +} diff --git a/yoga-api/.understand-anything/meta.json b/yoga-api/.understand-anything/meta.json new file mode 100644 index 00000000..ccdb64d2 --- /dev/null +++ b/yoga-api/.understand-anything/meta.json @@ -0,0 +1,36 @@ +{ + "projectName": "yoga-api", + "projectDescription": "GraphQL Yoga API for Application Tracker — a dual REST+GraphQL server built with GraphQL Yoga 5, Pothos code-first schema builder, and Prisma ORM with PostgreSQL. Manages job applications, interview stages, and application history with CSV import/export support. Paired with react-apollo-ui. Runs on port 5080 (graphql_yoga schema).", + "generatedAt": "2026-05-04T00:00:00.000Z", + "gitHash": "e5054096efec8165e7d4cfe11fc103f77ad2d98f", + "pluginVersion": "2.5.1", + "entryPoint": "src/index.ts", + "languages": ["TypeScript", "SQL", "Prisma Schema"], + "frameworks": ["GraphQL Yoga", "Pothos", "Prisma ORM", "Node.js HTTP", "Vitest"], + "totalFiles": 23, + "totalNodes": 22, + "totalEdges": 36, + "totalLayers": 5, + "tourSteps": 8, + "nodeTypes": { + "service": 5, + "config": 7, + "file": 9, + "schema": 1 + }, + "edgeTypes": { + "imports": 34, + "uses": 2 + }, + "layers": [ + "Infrastructure & Configuration", + "Business Logic Services", + "GraphQL Schema Layer", + "HTTP Server & API Gateway", + "Tests" + ], + "warnings": [ + "Config/migration files (package.json, tsconfig.json, vitest.config.ts, eslint.config.js, prisma.config.ts, migration SQL files) have no code-level import edges — expected for tooling config nodes", + "22 nodes defaulted to complexity 'moderate' from merge script (all were explicitly set in final graph)" + ] +}