Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# Normalize line endings — LF in repo, LF in working tree on all platforms.
* text=auto eol=lf

# Binary file types — never touch line endings.
*.png binary
*.jpg binary
*.jpeg binary
*.gif binary
*.ico binary
*.webp binary
*.zip binary
*.pdf binary
*.woff binary
*.woff2 binary
44 changes: 44 additions & 0 deletions .github/PULL_REQUEST_TEMPLATE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
## What

<!-- 1–3 sentences: what this PR does and why. If it closes one AC, say so here. -->

## Changes

### Code

<!-- file or module per bullet; what changed and why in one line. Skip if PR has no code. -->
-

### Tooling / config

<!-- package.json, tsconfig, vite/next config, env, gitignore, docker-compose, etc. Skip if none. -->
-

### Docs / plan

<!-- plan file, README, CLAUDE.md, .claude/skills/*, docs/*. Always include the plan link here. -->
- `plans/weekN/<name>.md` — design rationale
-

## Design decisions

<!-- 2–6 bullets of the key calls and their rationale. Cite the plan for full reasoning. -->
-

## Verification

<!-- checklist of what was actually tested locally. Sub-sections (Smoke tests / Manual UI / Screenshots) are optional. -->
- [ ] `npx tsc --noEmit` (dashboard) — clean
- [ ] `npm run lint` (dashboard) — clean
- [ ] `npm run typecheck` (extension) — clean
- [ ] Manual test: <!-- what you actually did, or "n/a" if docs/config only -->

## Out of scope (future PRs)

<!-- intentionally not in this PR — keeps reviewers from asking. Link the AC if it's already planned. -->
-

## Closes

<!-- e.g. Week 1 AC 5 (docs/Task.md#L59) — or "n/a" for repo-meta PRs without an AC. -->
-
46 changes: 46 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
name: CI

on:
pull_request:
branches: [development, main]
push:
branches: [development, main]

jobs:
dashboard:
runs-on: ubuntu-latest
defaults:
run:
working-directory: dashboard
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
cache-dependency-path: dashboard/package-lock.json
- run: npm ci
- run: npx prisma generate
env:
# prisma.config.ts uses prisma/config's strict env() helper, which
# throws if DATABASE_URL is unset. `prisma generate` does not connect
# to the database — it only reads the schema and writes the client —
# so a dummy URL satisfies the config loader.
DATABASE_URL: "postgresql://prisma:prisma@localhost:5432/prisma"
- run: npm run lint
- run: npx tsc --noEmit

extension:
runs-on: ubuntu-latest
defaults:
run:
working-directory: extension
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
cache-dependency-path: extension/package-lock.json
- run: npm ci
- run: npm run typecheck
1 change: 1 addition & 0 deletions .husky/pre-commit
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
npx lint-staged
1 change: 1 addition & 0 deletions .nvmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
20
94 changes: 94 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
# Contributing

Short reference for working in this repo. Full context, weekly roadmap, and acceptance criteria live in [docs/Task.md](docs/Task.md).

## Workflow: Plan → Review → Implement

Never start a non-trivial change by writing code. The loop is:

1. **Plan** — write a markdown file at `plans/weekN/<name>.md` describing scope, files touched, design decisions, and a verification checklist. The plan goes on the feature branch as its first commit (or alongside the implementation commit when small).
2. **Review** — the user reads the plan and approves (or asks for adjustments) before any code is written.
3. **Implement** — only after approval. The implementation should follow the plan; if reality diverges, update the plan in the same PR.

See [docs/Task.md § "Головне правило воркфлоу"](docs/Task.md#L180) for the rationale.

## Branches

Two protected branches:

- `main` — production. Direct pushes blocked. Only merges from `development` after final review.
- `development` — integration branch. Direct pushes blocked. All feature work merges here via PR.

Each task gets its own branch off `development`:

```
feature/<short-name> new functionality
fix/<short-name> bug fix
chore/<short-name> deps, config, repo meta — no logic changes
refactor/<short-name> behavior-preserving refactor
docs/<short-name> docs-only changes
```

PR → `development`. When a week (or milestone) is done: `development` → PR → `main`.

See [docs/Task.md § "Git воркфлоу"](docs/Task.md#L104) for full naming conventions and examples.

## Commit messages

[Conventional Commits](https://www.conventionalcommits.org/). Format:

```
<type>(<scope>): <short description>
```

Types: `feat`, `fix`, `chore`, `refactor`, `style`, `test`, `docs`.

- Description in English, lowercase, no trailing period.
- First line ≤ 72 characters.
- Body (after a blank line) only if extra context is needed.

Examples:

```
feat(extension): add content script for page metadata parsing
fix(popup): reset timer on service worker restart
docs(readme): add full root readme (AC 5)
chore(deps): update prisma to 7.9
```

Full guidance: [docs/Task.md § "Commit messages"](docs/Task.md#L144).

## Branch protection (one-time GitHub UI setup — repo owner)

Settings → Branches → add rule for each of `main` and `development`:

- [x] Require a pull request before merging
- [x] Require status checks to pass before merging
- Required checks (once CI lands): `CI / dashboard`, `CI / extension`
- [x] Do not allow bypassing the above settings

This step is GitHub UI only — it isn't expressible in a commit. Enable after the CI workflow has run at least once on `development` so the status check names become selectable.

## Where things live

Architectural invariants enforced in [CLAUDE.md](CLAUDE.md). Quick map:

| Concern | Location |
|---|---|
| Business logic (dashboard) | `dashboard/server/` — never inside React components or Route Handlers |
| Route Handlers | `dashboard/app/api/**/route.ts` — thin: parse → validate (Zod) → call `server/` → respond |
| Auth (Google token, JWT) | `dashboard/app/api/auth/**/route.ts` + `dashboard/middleware.ts` — nowhere else |
| Zod schemas for API input | `dashboard/server/schemas/<domain>.ts` — derived from `@/generated/zod/...`, never hand-written from scratch |
| Prisma schema + migrations | `dashboard/prisma/` |
| Extension service worker | `extension/src/background/` — only place that talks to the API or `chrome.storage.local` |
| Extension content/popup | `extension/src/content/`, `extension/src/popup/` — message the background, never `fetch` directly |
| Task recipes (for the AI) | `.claude/skills/` |
| Plans (per-AC) | `plans/weekN/` |

## Type safety

TypeScript strict. `any` is forbidden — use `unknown` + type guards, or define proper types. Enforced by `tsc --noEmit` in CI.

## `extension/` ↔ `dashboard/` isolation

Never import from `dashboard/` inside `extension/` or vice versa. They communicate only via HTTP (the API routes). Separate `package.json`, `tsconfig.json`, and `node_modules` per app.
21 changes: 21 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2026 BODMAT

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
116 changes: 113 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,116 @@
# WorkTrace

Browser extension + web dashboard that captures developer
session context and generates AI-powered reports.
Browser extension + web dashboard that captures developer session context and generates AI-powered session reports.

See [docs/Task.md](docs/Task.md) for full scope and acceptance criteria.
## What it is

A Chrome extension (Manifest V3) silently records the pages a developer opens during a coding session — URLs, titles, page metadata, tags, optionally the music they listen to — and ships them to a Next.js dashboard. The dashboard renders a per-session timeline and asks an LLM to summarize each session into a structured Markdown report.

See [docs/Task.md](docs/Task.md) for the full specification, acceptance criteria, and four-week roadmap.

## Repo layout

```
worktrace/
├─ dashboard/ Next.js 16 web app (App Router) + API routes + Prisma
├─ extension/ Chrome Manifest V3 extension (Vite + @crxjs/vite-plugin)
├─ docs/ Project specification (Task.md)
├─ plans/ Per-AC implementation plans (Plan → Review → Implement)
├─ .claude/skills/ Task recipes for the AI assistant
├─ CLAUDE.md AI agent rules (architecture invariants)
└─ docker-compose.yml Local PostgreSQL for development
```

The two apps are isolated — `extension/` never imports from `dashboard/` and vice versa. They communicate over HTTP only.

## Tech stack

- **Dashboard** — Next.js 16 (App Router, Server Components), React 19, Tailwind CSS v4, TanStack Query v5, Framer Motion, D3.js
- **Backend** — Next.js Route Handlers, Prisma 7 (with `@prisma/adapter-pg`), Zod, `google-auth-library`, `jsonwebtoken`
- **Extension** — Manifest V3, Vite 7 + `@crxjs/vite-plugin`, TypeScript (strict)
- **Database** — PostgreSQL 16 (Docker for local, Neon for production)
- **AI** — LLM provider TBD (Week 3): OpenAI / Groq / Gemini / OpenRouter
- **Deploy** — Vercel (dashboard), `.zip` for local extension install

Versions in `dashboard/package.json` and `extension/package.json` are authoritative.

## Prerequisites

- **Node.js** 20 or newer
- **Docker Desktop** (or Docker Engine + Compose plugin)
- **Chrome** 120+ (for loading the unpacked extension)

## Setup

### 1. Clone and configure env

```bash
git clone https://github.com/BODMAT/worktrace.git
cd worktrace
cp dashboard/.env.example dashboard/.env
cp extension/.env.example extension/.env
```

The dashboard `.env` contains `DATABASE_URL` (already pointing at the docker-compose Postgres) plus `GOOGLE_CLIENT_ID`, `GOOGLE_CLIENT_SECRET`, `JWT_SECRET`. The Google/JWT values are **not required for Week 1** — leave them blank until the auth flow lands in Week 2.

### 2. Start PostgreSQL

```bash
docker-compose up -d
```

This boots a `worktrace-postgres` container on host port **5433** (mapped to container 5432, so it won't collide with a local Postgres on 5432). The `DATABASE_URL` in `dashboard/.env.example` already uses this port.

### 3. Run the dashboard

```bash
cd dashboard
npm install
npx prisma migrate dev
npm run dev
```

The dashboard is now at `http://localhost:3000`. The `npm run dev` step also implicitly runs `prisma generate` on first build, materializing the Prisma client and Zod schemas into `dashboard/generated/` (both gitignored).

### 4. Build and load the extension

```bash
cd extension
npm install
npm run build
```

Then in Chrome:

1. Open `chrome://extensions`
2. Toggle **Developer mode** (top right)
3. Click **Load unpacked**
4. Select `extension/dist/`

The extension card should appear with no errors. For HMR-rebuild during active development use `npm run dev` instead of `npm run build`.

## Verifying it works

1. **Dashboard** — `http://localhost:3000` loads (Next.js placeholder page is fine for Week 1).
2. **Extension** — the card on `chrome://extensions` shows no errors; clicking the toolbar icon opens an empty popup; the service worker logs `[worktrace] installed` (visible via the extension's **Inspect** link).
3. **API** — a POST to the events endpoint round-trips through Postgres:

```bash
curl -X POST http://localhost:3000/api/v1/events \
-H "Content-Type: application/json" \
-d '{"sessionId":"ctestsess0000000000000001","url":"https://example.com","title":"Hello","tags":["test"],"timestamp":"2026-05-15T12:00:00Z"}'
```

A 201 response with the created event means the dashboard, Prisma, and Postgres are wired up correctly. (The `sessionId` must reference an existing `Session` row — create one via `npx prisma studio` or seed it manually.)

## Project docs

- [docs/Task.md](docs/Task.md) — full acceptance criteria, weekly roadmap, git workflow
- [CLAUDE.md](CLAUDE.md) — AI agent rules: architecture invariants, type-safety constraints, import boundaries
- [.claude/skills/](.claude/skills/) — task recipes (`api-route.md`, `prisma-model.md`, `extension-message.md`)
- [plans/](plans/) — per-AC implementation plans authored before each commit (the Plan → Review → Implement loop)

## Deploy

- **Dashboard** — Vercel with **Root Directory: `dashboard`**. The build command `prisma generate && next build` (declared in `dashboard/package.json`) regenerates the Prisma client at build time since `dashboard/generated/` is gitignored. Production database is Neon.
- **Extension** — distributed as a `.zip` of `extension/dist/` for local install. Chrome Web Store packaging is scheduled for Week 4 AC 2.
24 changes: 24 additions & 0 deletions dashboard/eslint.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,31 @@ const eslintConfig = defineConfig([
"out/**",
"build/**",
"next-env.d.ts",
// Generated by `prisma generate` — both `prisma-client` and `prisma-zod-generator`.
"generated/**",
]),
{
rules: {
// CLAUDE.md: `any` is forbidden. Use `unknown` + type guards or a proper type.
"@typescript-eslint/no-explicit-any": "error",
// Block `@ts-ignore`, `@ts-nocheck`, `@ts-expect-error` (the latter without a description).
"@typescript-eslint/ban-ts-comment": "error",
// Forbid `{ foo: bar } as SomeType` — a common source of silent bugs. Allow `value as SomeType`.
"@typescript-eslint/consistent-type-assertions": [
"error",
{ assertionStyle: "as", objectLiteralTypeAssertions: "never" },
],
// Forbid casting to `unknown` (incl. the `as unknown as T` escape hatch).
"no-restricted-syntax": [
"error",
{
selector: "TSAsExpression[typeAnnotation.type='TSUnknownKeyword']",
message:
"Casting to `unknown` (including `as unknown as T`) is forbidden. Use a proper type guard or define a real type.",
},
],
},
},
]);

export default eslintConfig;
1 change: 0 additions & 1 deletion dashboard/server/db.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import { PrismaPg } from "@prisma/adapter-pg";
import { PrismaClient } from "@/generated/prisma/client";

declare global {
// eslint-disable-next-line no-var
var prismaGlobal: PrismaClient | undefined;
}

Expand Down
Loading
Loading