diff --git a/docs/superpowers/plans/2026-06-17-phase-3a-admin-panel.md b/docs/superpowers/plans/2026-06-17-phase-3a-admin-panel.md
new file mode 100644
index 0000000..113fe53
--- /dev/null
+++ b/docs/superpowers/plans/2026-06-17-phase-3a-admin-panel.md
@@ -0,0 +1,320 @@
+# Phase 3a — Admin Panel (MVP) Implementation Plan
+
+> **For agentic workers:** Implement task-by-task with TDD. Steps use checkbox (`- [ ]`) syntax.
+> Spec: `docs/superpowers/specs/2026-06-17-phase-3a-admin-panel-design.md`.
+
+**Goal:** A new `apps/admin` desktop SPA where an admin logs in, watches who is working
+live (auto-refreshing, read-only), and manages handelingen — on existing endpoints plus
+`role` added to `/api/me`.
+
+**Architecture:** Vite + React 18 + TS client (dev port 5174) mirroring `apps/worker`'s
+toolchain. Bearer-token auth reused from worker; admin gate reads `role` from `/api/me`.
+Left-sidebar shell. Live view polls `/api/admin/sessions/active`; activities CRUD via
+`/api/activities`.
+
+**Tech Stack:** Vite 7, React 18.3, react-router-dom 6, @tanstack/react-query 5, vitest 3,
+@testing-library/react 16, TypeScript 5.7, `@solelog/shared` (zod contracts).
+
+## Global Constraints
+
+- **Reuse, don't reinvent:** `apps/worker` is the canonical template — copy `lib/api.ts`,
+ `lib/auth-storage.ts`, tsconfig/vite/vitest configs, `test/setup.ts`, `main.tsx`
+ structure verbatim, adjusting only names/title/port.
+- **Dutch UI strings** throughout (worker app is the reference for tone/terms).
+- **Lint/format:** oxlint + oxfmt — 2-space, single quotes, semicolons, width 100, **es5
+ trailing commas** (no trailing comma in function params or last call args). Run scoped to
+ changed files only; do **not** reformat unrelated files.
+- **TDD:** write the failing test, see it fail, implement minimally, see it pass, commit.
+- **Commit per task** with a conventional-commit message.
+- **No backend change beyond `role` on `/api/me`** (Task 1). Live view is **read-only**.
+- **Windows libsql lock trap:** any agent that starts the API server must kill the server
+ tree afterward (a lingering `tsx`/node holding `data/app.db` or port 3000 breaks the next
+ run). Prefer not starting the server unless verifying live.
+- Admin dev port **5174** (worker uses 5173).
+
+## File Structure
+
+```
+packages/shared/src/index.ts MODIFY add role to PublicUser
+apps/api/src/routes/me.ts MODIFY return role
+apps/api/test/me.test.ts MODIFY assert role present (create if absent)
+
+apps/admin/ NEW workspace (mirror apps/worker)
+ package.json, index.html, .gitignore, README.md
+ tsconfig.json, tsconfig.app.json, tsconfig.node.json
+ vite.config.ts (port 5174), vitest.config.ts
+ src/
+ main.tsx, vite-env.d.ts, styles.css
+ test/setup.ts
+ lib/api.ts (copied from worker)
+ lib/auth-storage.ts (copied from worker)
+ lib/elapsed.ts (formatTime ported from worker stopwatch)
+ auth/AuthContext.tsx (signIn + admin-role gate)
+ api/me.ts (useMe / fetchMe)
+ api/admin-sessions.ts (useActiveSessions, refetchInterval 5000)
+ api/activities.ts (useActivities + create/update/delete)
+ components/Sidebar.tsx
+ screens/Login.tsx, Live.tsx, Activities.tsx
+ App.tsx
+```
+
+---
+
+### Task 1: Backend — `role` on `/api/me`
+
+**Files:**
+- Modify: `packages/shared/src/index.ts` (PublicUser)
+- Modify: `apps/api/src/routes/me.ts`
+- Test: `apps/api/test/me.test.ts` (extend; or `apps/api/src/routes/me.test.ts` — match
+ where existing route tests live)
+
+**Interfaces:**
+- Produces: `PublicUser` now has `role: Role`; `MeResponse.user.role` is `'worker' | 'admin'`.
+
+- [ ] **Step 1: Write/extend the failing test.** Using the test helpers
+ (`apps/api/test/helpers.ts`: `createTestUser`, `authToken`/`bearer`), assert that
+ `GET /api/me` with a worker token returns `user.role === 'worker'`, and with an admin
+ token returns `user.role === 'admin'`. (Create an admin via `auth.api.createUser` with
+ `role: 'admin'` — see `seed.ts` for the cast pattern.)
+- [ ] **Step 2: Run it, watch it fail** (`role` undefined).
+ `yarn workspace @solelog/api test` (filter to the me test).
+- [ ] **Step 3: Add `role` to `PublicUser`** in shared:
+
+```ts
+export const PublicUser = z.object({
+ id: z.string(),
+ email: z.string().email(),
+ name: z.string(),
+ role: Role,
+});
+```
+
+- [ ] **Step 4: Return `role` from the route.** In `apps/api/src/routes/me.ts`, add to the
+ `user` body: `role: ((session.user as { role?: string | null }).role ?? 'worker') as Role`
+ (import `Role`/`MeResponse` type from `@solelog/shared`). Keep the `MeResponse` typing.
+- [ ] **Step 5: Run tests — pass.** Also run `yarn workspace @solelog/api typecheck`.
+- [ ] **Step 6: Confirm worker app unaffected** — `yarn workspace @solelog/worker test`
+ still green (it ignores the extra field).
+- [ ] **Step 7: Commit** — `feat(api): include role in /api/me response`.
+
+---
+
+### Task 2: Scaffold `apps/admin` workspace
+
+**Files:** all new under `apps/admin/` (see File Structure). Copy from `apps/worker`.
+
+**Interfaces:**
+- Produces: an installable `@solelog/admin` workspace that builds and runs an empty app
+ shell; `apiFetch`, `signIn`, `getToken/setToken/clearToken` available.
+
+- [ ] **Step 1: Copy config + boilerplate from worker**, adjusting only identifiers:
+ - `package.json` → `"name": "@solelog/admin"`, same scripts/deps/devDeps as worker.
+ - `index.html` → `
SoleLog Admin`.
+ - `tsconfig.json`, `tsconfig.app.json`, `tsconfig.node.json` → copy verbatim.
+ - `vite.config.ts` → `server: { host: true, port: 5174 }`.
+ - `vitest.config.ts`, `src/test/setup.ts`, `src/vite-env.d.ts` → copy verbatim.
+ - `.gitignore` → copy from worker.
+ - `src/lib/api.ts`, `src/lib/auth-storage.ts` → copy **verbatim** (token key
+ `solelog.token` is shared intentionally — same backend, same browser origin is fine;
+ admin runs on a different port so localStorage is separate anyway).
+- [ ] **Step 2: Minimal `src/main.tsx`** (copy worker's; renders `` inside
+ `QueryClientProvider`) and a placeholder `src/App.tsx` returning `SoleLog Admin
`
+ and an empty `src/styles.css`.
+- [ ] **Step 3: Smoke test** `src/App.test.tsx`: renders App, expects "SoleLog Admin" text
+ (wrap in `QueryClientProvider`).
+- [ ] **Step 4: Install + verify** — from repo root `yarn install`, then
+ `yarn workspace @solelog/admin test` (smoke passes), `yarn workspace @solelog/admin typecheck`,
+ `yarn workspace @solelog/admin build`.
+- [ ] **Step 5: Commit** — `feat(admin): scaffold Vite+React admin workspace`.
+
+---
+
+### Task 3: Auth context + admin gate + Login screen
+
+**Files:**
+- Create: `apps/admin/src/api/me.ts`, `apps/admin/src/auth/AuthContext.tsx`,
+ `apps/admin/src/screens/Login.tsx`
+- Modify: `apps/admin/src/App.tsx`
+- Test: `apps/admin/src/auth/AuthContext.test.tsx` (or `Login.test.tsx`)
+
+**Interfaces:**
+- Consumes: `signIn` from `lib/api`, `getToken/clearToken` from `lib/auth-storage`,
+ `MeResponse` from `@solelog/shared`.
+- Produces: `useAuth(): { isAuthed, signIn, signOut }` where `signIn` rejects non-admins;
+ `fetchMe(): Promise`.
+
+- [ ] **Step 1: Write failing tests** (mock `lib/api`):
+ - signing in as an admin (`/api/me` → `role: 'admin'`) sets `isAuthed` true;
+ - signing in as a worker (`role: 'worker'`) throws, clears the token, `isAuthed` false.
+- [ ] **Step 2: Run — fail** (`AuthContext` not implemented).
+- [ ] **Step 3: Implement `api/me.ts`:**
+
+```ts
+import type { MeResponse } from '@solelog/shared';
+import { apiFetch } from '../lib/api';
+
+export function fetchMe(): Promise {
+ return apiFetch('/api/me');
+}
+```
+
+- [ ] **Step 4: Implement `auth/AuthContext.tsx`** — mirror worker's, but `signIn` does:
+ call `apiSignIn(email,password)`; then `const me = await fetchMe()`; if
+ `me.user.role !== 'admin'` → `clearToken()` and `throw new Error('not-admin')`; else
+ `setIsAuthed(true)`. `signOut` clears token + sets false. Initial `isAuthed` =
+ `getToken() !== null` (a stale worker token is harmless — the admin endpoints 403 and the
+ next `/api/me`-backed screen can sign out; keep 3a simple).
+- [ ] **Step 5: Implement `screens/Login.tsx`** — copy worker's Login; change the catch to
+ set `'Geen toegang — alleen beheerders.'` when the error is the not-admin error, else
+ `'Inloggen mislukt'`. (Distinguish by error message/`instanceof`.)
+- [ ] **Step 6: Wire `App.tsx`** — `AuthProvider` + `Gate` (authed → shell placeholder,
+ else ``), following worker's `App.tsx`.
+- [ ] **Step 7: Run tests — pass.** typecheck.
+- [ ] **Step 8: Commit** — `feat(admin): bearer auth with admin-only gate + login screen`.
+
+---
+
+### Task 4: Sidebar shell + routing
+
+**Files:**
+- Create: `apps/admin/src/components/Sidebar.tsx`
+- Modify: `apps/admin/src/App.tsx`, `apps/admin/src/styles.css`
+- Test: `apps/admin/src/App.test.tsx` (replace the Task-2 smoke test)
+
+**Interfaces:**
+- Consumes: `useAuth` (signOut), `useMe`/`fetchMe` for the signed-in email.
+- Produces: an authed shell with `