Commit Graph

31 Commits

Author SHA1 Message Date
xarmian 0e32645bb5 feat: add TOTP two-factor authentication
Backend support for optional TOTP-based 2FA on user accounts:

- POST /auth/2fa/setup — generate TOTP secret, return QR code URI
- POST /auth/2fa/verify — verify code and enable 2FA with recovery codes
- POST /auth/2fa/disable — disable 2FA (requires password confirmation)
- POST /auth/2fa/login-verify — complete login with TOTP or recovery code
- Login returns {requires_2fa: true, user_id} when 2FA is enabled,
  requiring a second step via /auth/2fa/login-verify
- 8 recovery codes generated on setup for account recovery
- User model extended with totp_secret, totp_enabled, recovery_codes
- Refactored user queries with shared scanUser/userColumns for DRYness

Implements TASK-169 under PLAN-15 (Pad Cloud: Hardening).
2026-04-08 20:30:39 +00:00
xarmian ba8e20c697 feat: add API token rotation, expiry defaults, and scope enforcement
- New tokens get a default 90-day expiry (configurable via platform
  settings: token_default_expiry_days, token_max_lifetime_days)
- POST /api/v1/auth/tokens/{id}/rotate generates a new secret while
  preserving token metadata; old secret is immediately invalidated
- X-Token-Expires-Soon and X-Token-Expires-At headers warn when a
  token is within 7 days of expiry
- Token scopes are now enforced: "read" restricts to GET/HEAD/OPTIONS,
  "write" and "*" allow all methods
- Existing tokens without expiry continue to work (backward compatible)

Implements TASK-170 under PLAN-15 (Pad Cloud: Hardening).
2026-04-08 18:00:43 +00:00
xarmian bde15d45ca Rename Phases to Plans, clean up deprecated aliases (#71)
* Rename "Phases" to "Plans" and clean up deprecated phase aliases

Renames the default "Phases" collection to "Plans" across the full stack:
- DB migration renames existing collections in-place (name, slug, prefix PLAN, icon 🗺️)
- Removes all deprecated Phase* backward-compat aliases from models and store
- Removes --phase CLI flag (use --parent instead)
- Updates convention triggers: on-phase-start/complete → on-plan-start/complete
- Updates dashboard API: active_phases → active_plans, /phases-progress → /plans-progress
- Updates all frontend components, types, and documentation

Closes IDEA-124

* Fix CSRF cookie not being cleared on logout

The SessionAuth middleware was re-issuing a CSRF cookie before the
logout handler could clear it, resulting in two Set-Cookie headers.
Skip CSRF re-issue for /api/v1/auth/ paths since auth endpoints
manage their own CSRF cookies (login sets, logout clears).

* Fix migration issues found in Codex review

- P1: Move doc_type UPDATE from migration 024 into 025, which recreates
  the table with the new CHECK constraint first (SQLite enforces CHECK
  on UPDATE, so the old constraint would reject 'plan')
- P1: Add PostgreSQL migration 005 for the collection rename (phases →
  plans) — previously only existed on the SQLite path
- P2: Recreate FTS triggers, indexes, and rebuild FTS after the table
  swap in migration 025 (DROP TABLE drops associated objects in SQLite)

* Fix parent filter field name and sync .agents skill copy

Codex review round 2 findings:
- P1: Parent filter compared against `parent_id` (wrong) instead of
  `parent_link_id` — plan filtering in collection view was broken
- P1: .agents/skills/pad/SKILL.md still had old --phase flags and
  "Phases" references — synced from the updated .claude copy
- P2: Accept legacy 'phase' filter key for backward compat with
  existing saved views that serialized the old key name

* Fix PG migration JSONB casting and add slug collision guards

Codex PR review bot findings:
- P1: PostgreSQL REPLACE/LIKE don't work on JSONB columns — cast
  schema::text and fields::text before string ops, then back to ::jsonb
- P1: If a workspace already has a custom 'plans' collection, the
  rename hits UNIQUE(workspace_id, slug) — added NOT EXISTS guard
  to both SQLite and PostgreSQL migrations
2026-04-07 14:55:23 -04:00
xarmian 063ff92d00 feat: generalized parent/child items with progress tracking (#70)
* feat: generalize parent/child items — any item can have children with progress tracking

Replaces the phases-only task widget with a generalized parent/child system.
Any item (Phase, Idea, Doc, Task, etc.) can now be a parent of child items,
getting automatic progress bars, burndown charts, status grouping, drag-drop
reordering, and recursive expand/collapse up to 3 levels deep.

DB: migrate link_type 'phase' → 'parent' (migration 023)
Store: generalized methods (GetChildItems, GetItemProgress, SetParentLink
  with cycle detection), drop collection filters, per-child terminal status
API: new /items/{slug}/children and /items/{slug}/progress endpoints
Frontend: ChildItems, ChildChart, NestedChildren components replace PhaseTasks
CLI: --parent flag (--phase kept as hidden alias), list/show/changelog updated
Docs: CLAUDE.md, SKILL.md, pad-web updated for parent/child model

Full backward compatibility: old 'phase' link_type, --phase flags, phase_id/
phase_ref JSON fields all still work as deprecated aliases.

Closes PHASE-16 (9 tasks).

* fix: update collection list page to use item_id from phasesProgress response

The TS client return type changed from phase_id to item_id but the
collection list page still referenced p.phase_id, causing svelte-check
type errors in CI.

* fix: address Codex review findings — PG migration, terminal statuses, metrics, CSRF resilience

- Add PostgreSQL migration 003 to rename 'phase' links to 'parent'
- Use schema-defined terminal statuses in GetAllItemProgress instead of hardcoded defaults
- Pass computed terminal statuses from page to ChildItems component
- Only special-case 'parent' field key when not defined in collection schema
- Move MetricsMiddleware before Recoverer so panics are counted
- Always mount ChildItems for SSE subscriptions even with 0 children
- Exclude soft-deleted children from has_children enrichment query
- Re-issue CSRF cookie when session is valid but cookie is missing
- Show actual API error messages in create-item toasts
2026-04-07 09:52:31 -04:00
xarmian 872f08aa84 feat: add compliance audit trail with IP/UA tracking
Extend the activities table to capture IP address and user agent for all
state-changing operations. Add audit events for auth (login, logout,
register, bootstrap, password changes), workspace management (member
invite/remove, role changes), token lifecycle, and admin settings.

- SQLite migration recreates activities table with nullable workspace_id,
  new ip_address/user_agent columns, and relaxed CHECK constraints
- PostgreSQL migration adds columns and drops constraints
- New ListAuditLog store method with action/actor/workspace/date filters
- GET /api/v1/audit-log endpoint (admin-only)
- CLI: pad workspace audit-log [--days N] [--actor X] [--action X]
2026-04-06 01:55:06 +00:00
xarmian 367116b3a0 Unify relation fields and item links into single dependency system (#66)
* feat: unify relation fields and item links into single dependency system

Phase membership (Task→Phase) was previously stored as a UUID in the
item's fields JSON, separate from the item_links table used for
blocks/related/implements relationships. This unifies both into the
item_links table so all item relationships use one system.

Backend:
- Add 'phase' link type to item_links constants
- Migration 021: migrate existing phase field values to item_links,
  strip phase from fields JSON, remove phase field from tasks schema
- Rewrite GetPhaseProgress, GetAllPhasesProgress, GetTasksForPhase
  to JOIN on item_links instead of json_extract(fields, '$.phase')
- Add SetPhaseLink, ClearPhaseLink, GetPhaseForItem, GetTaskPhaseMap
  store helpers with single-phase constraint enforcement
- Create/update handlers intercept 'phase' in fields and route through
  links system; enrich item responses with phase_id/ref/title
- Dashboard orphan detection uses batch GetTaskPhaseMap lookup
- Add PhaseID filter to ItemListParams for link-based list filtering

Frontend:
- Remove relation field type from FieldEditor (no longer needed)
- Add link CRUD UI to item detail page: "Add relationship" inline form
  with link type picker + item search, delete buttons on existing links
- Phase links appear in Relationships section as "In phase"/"Phase"
- ItemCard reads phase from item.phase_title instead of fields.phase
- FilterBar phase filter uses item.phase_id for client-side filtering
- Add api.links.delete to frontend API client
- Fix duplicate {#each} key on dashboard attention list

Implements IDEA-106.

* fix: remove relationLabels prop from BoardView, ListView, TableView

ItemCard no longer accepts relationLabels (phase info now comes from
item.phase_title), so remove the prop from all parent view components
that were passing it through. Also remove unused .cell-relation CSS.

* fix: address PR review — atomic SetPhaseLink, migration safety, error handling

1. Migration 021: remove deleted_at filters so archived tasks and tasks
   pointing to archived phases also get their phase links migrated.

2. SetPhaseLink: wrap delete+insert in a transaction so a failed insert
   doesn't leave the item with no phase link (previously non-atomic).

3. Create/update handlers: return proper HTTP errors when phase link
   operations fail instead of logging warnings and returning 200 OK.
2026-04-04 19:47:57 -04:00
xarmian e21da3c6a4 fix: schema-driven terminal statuses replace hardcoded lists (BUG-17) (#65)
Add `terminal_options` to collection field schemas so each collection
declares which statuses are terminal/finalized. Replaces 10+ inconsistent
hardcoded status lists across backend, CLI, and frontend.

- Add TerminalOptions to FieldDef and centralized helpers in models/terminal.go
- Populate terminal_options on all default and template collections
- Replace hardcoded isDoneStatus/isTerminalItemStatus with schema-aware lookups
- Fix phase progress to count all terminal statuses (not just "done")
- Add terminal status toggle UI in collection field editor (Settings → Fields)
- Redesign collection field editor for cleaner layout and alignment
- Move Platform settings tab before Danger Zone tab
2026-04-04 18:56:30 -04:00
xarmian 8b1b46ef22 feat: persistent card ordering within role board lanes
Add role_sort_order column to items for independent ordering in the
role board, separate from the collection sort_order.

Backend:
- Migration 020: role_sort_order INTEGER column on items
- Item model, all SELECT/INSERT/Scan queries updated
- PUT /roles/board/reorder endpoint for batch sort updates
- Board API sorts items by role_sort_order within each lane

Frontend:
- Within-lane drag reorder persists via reorder API
- Cross-lane moves also persist new sort order
- Both operations are optimistic (no page refresh)
2026-04-04 14:00:21 +00:00
xarmian edd259b1b0 feat: role board — cross-collection view, dashboard breakdown, agent bindings (#60)
* feat: role board — cross-collection view, dashboard breakdown, agent bindings (#PHASE-11)

Add a standalone role board page showing all work organized by agent
role across every collection. This is the "human orchestrator" view —
see at a glance what's queued for each capability and who's working it.

Agent bindings:
- Add `tools` text field to agent_roles table (migration 019)
- CLI: `pad role create "Implementer" --tools "Claude Code + Sonnet"`
- Lightweight notes about preferred tools — no per-user binding table

Dashboard role breakdown:
- `pad project dashboard` now includes `by_role` section
- Shows item count, assigned users, and tools per role
- CLI renders role summary table with icons

Role board API:
- `GET /workspaces/{ws}/roles/board` — items from all collections grouped by role
- Filters terminal-status items (done, cancelled, etc.)
- Supports `?assigned_user_id=X` for "my work" filtering
- Returns role info, items, and assigned user list per lane

Web UI:
- New page at /{workspace}/roles with horizontal lane layout
- Collection badges on cards (items span collections)
- "My Work" toggle to filter by current user
- Empty states for no roles and empty lanes
- Sidebar nav: 🎭 Roles link added
- Responsive: stacks vertically on mobile

Skill:
- References role board in greeting and "who's working on what" patterns

* feat: add assignment picker to item detail page

Replace read-only assignment display with interactive dropdowns for
assigning users and roles directly from the item detail page.

- User dropdown populated from workspace members
- Role dropdown populated from agent roles
- Either can be set or cleared independently
- Saves immediately on change via PATCH API
- Added assigned_user_id/agent_role_id/clear_* to ItemUpdate type

* feat: add role management UI to roles page

Add a "Manage" toggle in the role board header that reveals an inline
panel for creating, editing, and deleting roles directly from the UI.

- Role cards show icon, name, description, tools, and item count
- Edit inline: name, icon, description, tools
- Create new roles with a dashed card form
- Delete with confirmation dialog
- Board auto-refreshes after changes

* refactor: replace inline role management with dialog modal

The inline horizontal card grid was cramped and hard to use. Replace
with a proper <dialog> modal that opens from the ⚙ Manage button.

- Vertical list of role rows with icon, name, description, tools, item count
- Inline edit mode per row with labeled fields
- Create new role form at the bottom with clear field labels
- Click backdrop or ✕ to close, board refreshes on close
- Native dialog handles backdrop, escape key, and focus trapping

* fix: role board mobile layout matches collection kanban, unassigned first

- Unassigned lane now appears first (before role lanes)
- Mobile: horizontal swipe with scroll-snap at 75vw columns, matching
  the collection BoardView pattern (no vertical stacking)

* feat: add drag-and-drop between role board lanes

Items can now be dragged between role lanes to reassign their role.
Uses svelte-dnd-action matching the collection BoardView pattern.

- Drag items between role lanes to change role assignment
- Drag to Unassigned lane to clear role
- Drop target highlight on hover
- Touch support with 500ms delay (same as collection board)
- Haptic feedback on mobile drag start
- Board refreshes after drop to sync server state

* fix: auto-assign user on drag to role lane, show unassigned in My Work

- When dragging an unassigned item into a role lane, automatically
  assign the current user alongside the role
- "My Work" filter now shows items assigned to you OR items with no
  user assignment, so unassigned work remains visible and claimable

* fix: three-state filter on role board — All, My Work, Unassigned

Replace the My Work toggle with a segmented button group offering
three filter modes:
- All: show everything (default)
- My Work: items explicitly assigned to the current user
- Unassigned: items with no user assignment

* fix: replace filter buttons with Highlight Mine toggle

Remove the three-state filter (All/My Work/Unassigned) and replace
with a single "Highlight Mine" toggle that dims cards not assigned
to the current user. All items remain visible and draggable — your
items just visually pop while others fade to 35% opacity (hovering
restores to 70%).

* fix: resolve undefined loadBoard and myWorkOnly in role board page

Replace 6 references to nonexistent `loadBoard()` with `loadData()`
(the actual data-loading function), and replace `myWorkOnly` with
`highlightMine` (the actual state variable). Fixes svelte-check errors
that caused CI Web Build to fail.

* fix: role breakdown pointer aliasing and terminal status filtering

P1: Copy role.ID to a local variable before taking its address in
GetRoleBreakdown, avoiding potential pointer aliasing from the range
variable (safe in Go 1.22+ but clearer with an explicit copy).

P2: Add terminal status exclusion to the GetRoleBreakdown SQL query
so dashboard counts match the board view. Previously, done/completed/
cancelled items were included in role counts, inflating active load.

Addresses Codex review comments on PR #60.
2026-04-04 07:48:33 -04:00
xarmian be576d9e24 feat: agent roles — role-based (user, role) assignment for items (#58)
* feat: agent roles — role-based (user, role) assignment for items (#PHASE-9)

Introduce agent roles as a first-class concept for human-agent work
assignment. Roles describe capability specializations (Planner,
Implementer, Reviewer, etc.) and items can be assigned to a (user, role)
pair, enabling natural handoff workflows between different AI tools.

Migration:
- New `agent_roles` table (workspace-scoped, slug-unique)
- `assigned_user_id` + `agent_role_id` columns on `items` with FKs
- Removed legacy `assignee` text field from Tasks schema

Backend:
- AgentRole model + full CRUD store/API
- All item queries updated with LEFT JOINs to resolve assignment
- Item list filtering by assigned_user_id and agent_role_id
- Role transitions tracked in activity feed metadata

CLI:
- `pad role list/create/delete` commands
- `--role` and `--assign` flags on item create/update/list
- Assignment displayed in `pad item show` output

Web:
- TypeScript types + API client for agent roles
- Role badge on item cards in list/board views
- Assignment display on item detail page

* fix: enforce workspace-scoped assignments and fail fast on unresolved --assign filter

Addresses code review feedback from PR #58:

P1: Add validateAssignmentScope() to the store layer, called by both
CreateItem and UpdateItem. Verifies that assigned_user_id belongs to
the workspace (via IsWorkspaceMember) and agent_role_id exists in the
workspace (via GetAgentRole) before writing. Prevents cross-workspace
assignment leaks.

P2: The CLI `pad item list --assign <name>` now errors instead of
silently returning unfiltered results when the member lookup fails or
no workspace member matches the provided name.
2026-04-03 21:16:38 -04:00
xarmian 481527de02 fix: server-side timeline pagination and reaction toggle (#57)
* fix: server-side timeline pagination and reaction toggle (#55, #56)

Issue #55: Replace in-memory pagination with cursor-based approach.
The /timeline endpoint now accepts `before` (RFC3339 timestamp) and
`limit` params, fetching a small window from each source (comments,
activities, versions) instead of loading everything into memory.
Frontend gets a "Load more" button that passes the oldest entry's
timestamp as the cursor.

Issue #56: Plumb current user ID to reaction toggle. ItemTimeline
fetches the auth session on mount to get the user ID, passes it to
TimelineCommentCard. toggleReaction now checks if the user already
reacted (by matching reaction.user_id) and calls onRemoveReaction
to un-react. Own reaction chips are visually highlighted.

* fix: address review findings for PR #57 (iteration 1)

- Use <= with ID tie-breaker in cursor queries to prevent skipping
  entries at timestamp boundaries; use consistent RFC3339 format
- Treat orphaned replies (parent on different page) as top-level
  entries instead of silently dropping them
- Deduplicate by ID on "Load more" to handle boundary overlap
- SSE reload now prepends new entries and updates existing ones
  instead of clobbering all paginated state
- Parse before cursor with RFC3339Nano fallback for sub-second
  precision
- Fix dangling doc comment on ListItemVersions

Co-Authored-By: Claude <noreply@anthropic.com>

* refactor: add global auth store, remove per-component session fetch

Create authStore (web/src/lib/stores/auth.svelte.ts) following the
same pattern as workspaceStore. The root layout populates it on mount.
ItemTimeline now reads currentUserId via $derived(authStore.userId)
instead of making a separate api.auth.session() call on every mount.

* fix: address review findings for PR #57 (iteration 2)

- Add beforeID tie-breaker to all cursor queries to prevent infinite
  Load More loop when entries share the same timestamp
- Over-fetch per source (limit*3) to avoid skipping filtered entries
- SSE refresh now detects deleted entries and removes them from the
  first-page window instead of keeping stale data
- Auth store re-throws on fetch errors so layout can distinguish
  "not authenticated" from "server unreachable"
- Sidebar reads from authStore instead of making redundant session fetch

Co-Authored-By: Claude <noreply@anthropic.com>

* fix: address review findings for PR #57 (iteration 3)

- Add ID tie-breaker to buildTimeline sort to match SQL cursor ordering
  (prevents same-second entries from being skipped on Load More)
- Refresh authStore after login so Sidebar and reaction toggle have
  correct user identity without requiring a page reload
- SSE merge now tracks first-page IDs explicitly to detect deletions
  without incorrectly removing entries from older pages that share
  boundary timestamps

Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-04-03 09:39:40 -04:00
xarmian 998716ae49 feat: unified item timeline with comment-on-update, threading, and reactions (#54)
* feat: unified item timeline with comment-on-update, threading, and reactions (IDEA-115)

Replace the separate Comments section and Version History modal on the
item detail page with a single chronological timeline that interleaves
comments, activities, and content versions.

Key changes:
- Add --comment flag to `pad item update` so agents/users can explain
  status changes inline (creates a comment linked to the activity)
- Add threaded replies (parent_id on comments) with inline reply UI
- Add emoji reactions on comments (new comment_reactions table)
- New /timeline API endpoint merges comments, activities, and versions
  server-side with dedup and collapsing of rapid edits
- New Svelte timeline components: ItemTimeline, TimelineCommentCard,
  TimelineActivityCard, TimelineVersionCard, ReactionPicker
- Remove Implementation Notes and Decision Log inputs from web UI
- Update skill docs to encourage --comment on status changes

* fix: address review findings for PR #54 (iteration 1)

- Use ListItemVersions instead of ListVersions in timeline endpoint
  so item content history renders correctly
- Add workspace validation to reply and reaction handlers to prevent
  cross-workspace comment mutation
- Raise activity cap from 500 to 10000 to avoid silently truncating
  long timelines
- Fix toggleReaction to always POST (idempotent) instead of incorrectly
  matching other users' reactions for DELETE
- Await onReply promise before clearing draft to prevent duplicate
  submissions and lost drafts on failure
- Register reaction_added/reaction_removed in SSE ITEM_EVENTS so
  reactions from other sessions appear in real-time

Co-Authored-By: Claude <noreply@anthropic.com>

* fix: address review findings for PR #54 (iteration 2)

- Fix nil pointer dereference in timeline handler when item not found
- Store empty string instead of NULL for reaction user_id so UNIQUE
  constraint works correctly in SQLite
- Add workspace validation to handleDeleteComment (cross-workspace
  deletion was possible)
- Fix SKILL.md duplicate numbering (4. appeared twice)
- Remove || true debug artifacts from reaction conditionals
- Replace SvelteMap with plain Map in non-reactive groupReactions
- Use != null checks for timeline API params to handle offset=0
- Log warning on comment creation failure during item update

Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-04-03 07:25:56 -04:00
xarmian 35f3dd1da4 feat(conventions): add structured metadata for TASK-133 (#51)
* feat(conventions): add structured metadata for TASK-133

* fix(web): add workspace update type for CI
2026-04-02 18:39:51 -04:00
xarmian 23a7fc2be1 feat(workspaces): add CLI and API context support for TASK-130 (#46) 2026-04-02 16:10:09 -04:00
xarmian c1a9cd1533 feat(workspaces): add typed context schema for TASK-129 (#47) 2026-04-02 16:09:10 -04:00
xarmian c61f4cdaaa feat(items): add structured notes for TASK-125 (#43) 2026-04-02 13:48:00 -04:00
xarmian bd9f281c81 feat(items): add first-class code metadata for TASK-123 (#41) 2026-04-02 11:15:38 -04:00
xarmian 0596ed07f4 feat(lineage): surface derived closure for TASK-122 (#40) 2026-04-02 10:46:09 -04:00
xarmian c9ba893650 feat(links): add lineage relationships for TASK-121 (#39) 2026-04-02 09:45:41 -04:00
xarmian 4003772538 feat: show user names in activity feeds and real-time events (#17)
Activity entries now display the authenticated user's name instead of
generic "user"/"You" labels. The user_id (already present in the
activities table from migration 012) is persisted when logging activity
and joined against the users table when querying, so actor_name flows
through the API without extra lookups. Falls back to the actor field
value when no user is associated (e.g. pre-auth activities or deleted
users).

Backend:
- Activity model gains UserID and ActorName fields
- Store queries LEFT JOIN users to populate actor_name
- logActivityWithMeta now records currentUserID on every activity
- Dashboard API includes actor_name in recent_activity
- SSE Event struct carries ActorName for real-time toasts
- Item-level activity endpoint added (GET /items/{slug}/activity)

Frontend:
- Activity page shows user name badge (green) instead of "web"
- Dashboard recent activity shows user name inline
- ActivityFeed component displays user name instead of "You"
- SSE toast notifications show user name instead of "CLI"
2026-03-28 19:11:42 -04:00
xarmian a219f81633 fix: CLI and skill file now use issue IDs (TASK-5) instead of slugs (#15)
Agents were using verbose slugs because:
1. The skill file (SKILL.md) taught them to use `<slug>` in every example
2. CLI output showed slugs in parentheses rather than issue IDs
3. CLI usage strings said `<slug>` not `<ref>`
4. JSON output lacked a `ref` field, so agents parsing JSON only saw slugs

Changes:
- Add computed `ref` field to Item model (e.g. "TASK-5") in JSON output
- CLI create/update/delete/edit output now prominently shows issue IDs
- All CLI usage strings changed from `<slug>` to `<ref>`
- Issue IDs displayed in bold cyan (not dim) in list/show/grouped views
- Skill file rewritten to use issue IDs in all examples and instructions
- Dashboard API includes `item_ref`/`ref` in attention, suggestions, phases
- Search results now include item_number and collection_prefix for refs
- CLAUDE.md updated to document issue ID usage
2026-03-28 16:14:32 -04:00
xarmian 46447e5504 feat: user management & authentication (Phase 6) (#14)
* feat: add user management database migration and models

Add migration 012_users.sql with users, sessions, and workspace_members
tables. Add user_id columns to api_tokens, items, comments, activities,
item_links, and item_versions for proper user attribution. Create Go
model structs (User, Session, WorkspaceMember) in models/user.go.

* feat: add store layer for users, sessions, and workspace members

Implement CRUD operations for user management:
- users.go: create, get, update, list, validate password (bcrypt)
- sessions.go: create, validate, delete, cleanup expired (SHA-256 hashed tokens)
- workspace_members.go: add/remove members, role management, access checks

Adds golang.org/x/crypto/bcrypt dependency. Includes 16 new tests
covering all store methods, password validation, session lifecycle,
and workspace membership operations.

* feat: rewrite auth system from single-password to user-based

Replace single-password auth with email/password user authentication:
- New endpoints: POST /auth/register, GET /auth/me
- Rewritten: POST /auth/login (email+password), GET /auth/session
  (needs_setup detection), POST /auth/logout (DB session destroy)
- Delete in-memory SessionManager, use DB-backed sessions via store
- New middleware: SessionAuth (cookie→user), RequireAuth (with
  fresh-install passthrough when no users exist)
- Remove Password field from config, PAD_PASSWORD env var, SetPassword()

All 23 existing server tests pass (fresh DBs have no users → passthrough).

* feat: add workspace access control middleware

Add RequireWorkspaceAccess middleware that checks workspace_members for
authenticated users, with fallback for legacy API tokens and fresh
installs (no users → implicit owner). Includes role hierarchy helpers
(workspaceRole, requireRole) for downstream permission checks.
Wire middleware into the /{slug} workspace route group.

* feat: add CLI auth commands and credential storage

Add pad login, pad logout, pad whoami commands with credential
storage in ~/.pad/credentials.json (0600 permissions). Update CLI
HTTP client to auto-attach auth tokens and X-Pad-Agent header on
all requests. Add auth API methods (Login, Register, Logout,
CheckSession, GetCurrentUser). Extend .pad.toml with optional
agent_name field. Add golang.org/x/term for masked password input.

* feat: derive actor/source from auth context in all handlers

Replace hardcoded "user"/"web" actor/source strings with auth-aware
helpers. actorFromRequest() derives actor ("user"/"agent" via
X-Pad-Agent header) and source ("web"/"cli" from auth method).
agentMeta() merges agent name into activity metadata. Update all
item, document, comment, and move handlers to use request-based
logActivity/logActivityWithMeta. Remove hardcoded CreatedBy/Source
from all CLI commands — server now determines these from auth context.

* feat: frontend auth — login, registration, auth guard, user menu

Rewrite login page with email/password fields, add registration page
for first-time setup, update auth guard to handle needs_setup redirect.
Add user menu to sidebar with logout. Update API client with new auth
methods (register, login with email, session with needs_setup flag).

* feat: migrate API tokens from workspace-scoped to user-owned

API tokens now have a user_id owner and optional workspace_id scope.
CreateAPIToken takes userID as first parameter. ValidateToken resolves
the token's user into the request context. TokenAuth middleware now
sets ctxCurrentUser when a user-owned API token is used. Add user-
scoped endpoints: GET/POST/DELETE /auth/tokens. Keep workspace-scoped
token endpoints for backwards compatibility.

* feat: workspace membership, invitations, and role enforcement

Add workspace_invitations table (migration 013) with join codes.
Implement invitation store methods (create, get by code, accept,
list). Add member management handlers: list members + invitations,
invite (auto-adds existing users or creates invitation), remove
member, change role, accept invitation by code. Add API routes
under /workspaces/{slug}/members/* and /invitations/{code}/accept.
Add CLI commands: pad members, pad invite, pad join.

* feat: auth tests and documentation updates

Add comprehensive auth endpoint tests: registration flow (first user
becomes admin), login/logout, validation errors, duplicate email,
auth enforcement (401 after users exist, exempt paths), /me endpoint.
Update CLAUDE.md and README.md to document user-based auth system,
replacing old PAD_PASSWORD references with pad login/members/invite
workflow and role-based access control.

* feat: add members management UI to workspace settings page

Add Members section to settings with: member list (avatar, name,
email, role), role change dropdown (owner only), remove button
(owner only), pending invitations display with join codes, and
invite form with email + role picker. Add members API methods to
the TypeScript client (list, invite, remove, updateRole).

* fix: backfill workspace owners for pre-migration workspaces

Add backfillWorkspaceOwners() that runs on server start. For any
workspace with no members, adds the first admin user as owner.
This handles the migration case where workspaces existed before the
user system — without it, the members list shows empty.

* feat: shareable invite links with /join/[code] page

Replace raw join codes with full shareable URLs. Server generates
join_url using its configured base URL (e.g. https://pad.example.com/
join/a3f8b2c1). New /join/[code] page handles the full flow: checks
auth → shows login/register if needed → accepts invitation → redirects
to workspace. Settings page shows "Copy invite link" button that copies
URL to clipboard. CLI outputs shareable link instead of raw code.

* fix: auto-add workspace creator as owner, integrate auth into pad init

handleCreateWorkspace now adds the authenticated user as owner of the
new workspace immediately — no more relying on the startup backfill.

pad init now checks auth status before making API calls. If no users
exist, prompts to register. If not logged in, prompts to login. After
auth, proceeds with workspace creation normally.

* fix: add join_url to invite response type in API client

* fix: address codex review — invite registration, logout token revocation, workspace scoping

- Allow registration with valid invitation_code (fixes invite flow for new users)
- Revoke Bearer session tokens on logout, not just cookies
- Filter workspace listing to user's memberships (admins see all)
2026-03-28 15:43:09 -04:00
xarmian 52c8348361 fix: enrich activity log entries with item titles and collection info (#1)
The activity list endpoint returned raw entries without item context,
causing the activity page to show bare "Created"/"Updated" verbs.
The dashboard already enriched entries via GetItem() lookups — now the
activity handler does the same, and the frontend reads top-level fields
with metadata fallback.
2026-03-28 10:51:36 -04:00
xarmian cf83a60fc2 feat: Add API tokens system for programmatic access
Add a complete API tokens system enabling CI/CD integrations, custom
scripts, and third-party tools to authenticate with the Pad API.

- Migration 011: api_tokens table with hash-based token storage
- Model: APIToken, APITokenCreate, APITokenWithSecret types
- Store: CRUD operations with crypto/rand generation and SHA-256 hashing
- Middleware: Bearer token auth that sets workspace context
- Handlers: POST/GET/DELETE /workspaces/{ws}/tokens endpoints
- CORS: Allow Authorization header for token-based requests
2026-03-28 14:13:50 +00:00
xarmian dc83d7490c feat: Add content templates for collections
Collections can now define a content_template in their settings — a
markdown template that pre-fills new items. When creating a bug report,
for example, the template can include "Steps to Reproduce", "Expected
Behavior", "Actual Behavior" sections automatically.

- Add content_template to CollectionSettings (Go model + TypeScript type)
- Sidebar "New" button uses template when creating items
- Dashboard quick-create buttons use template
- Template is stored in collection settings JSON, configurable via
  the collection edit UI
2026-03-28 14:07:07 +00:00
xarmian 86722d7607 feat: Add webhooks, saved views, enhanced dashboard, and dependencies backend
Webhooks:
- Full subsystem with HMAC-SHA256 signing, event filtering, auto-disable
  after 10 failures, test delivery endpoint
- Migration 010_webhooks.sql, model, store CRUD, dispatcher with tests
- Wired into item create/update/delete/move and comment create handlers

Dashboard API:
- active_items: Returns actual in-progress items with refs and priorities
- active_item_count on collections (excludes terminal statuses)
- Blocked item detection in attention (via dependency links)
- isDoneStatus expanded to include cancelled/rejected/fixed/implemented

Saved Views:
- Store CRUD, API handlers, routes for per-collection saved views
- View config stores filters, sort, view_type

Activity:
- Source filtering support (web/cli/agent) in activity list endpoint
2026-03-28 13:55:06 +00:00
xarmian 6daa8eb68b Add move item between collections with field migration
Full-stack feature: move items between collections (e.g., idea → task)
with automatic field migration.

Backend:
- Field migration engine (items/migrate.go) maps matching fields,
  handles type conversions, drops incompatible fields, applies defaults
- Store method updates collection_id and assigns new item_number
- POST /api/v1/workspaces/{ws}/items/{slug}/move endpoint
- Activity logging with "moved" action and from/to metadata
- 6 migration unit tests covering type matching, conversion, and edge cases

CLI:
- pad move <slug> <target-collection> [--field key=value ...]
- Accepts singular collection names (task, idea, bug, etc.)

Web UI:
- "Move to..." dropdown on item detail page
- Shows all collections except current with icons
- Redirects to the item's new URL after move

Field migration rules:
- Same type: transfer directly (validate select options)
- Compatible types (text↔url, number→text, select→text): auto-convert
- Incompatible types: drop silently
- Missing required target fields: apply defaults or error
2026-03-28 05:01:40 +00:00
xarmian dbecb741c6 Add workspace export/import and fix create workspace modal
Export/Import:
- `pad export -o file.json` exports workspace (collections, items,
  comments, links, versions) to portable JSON
- `pad import file.json --name X` creates new workspace with
  regenerated UUIDs and remapped relations
- GET /workspaces/{slug}/export and POST /workspaces/import endpoints
- "Download JSON" button in workspace settings
- Import tab with drag-and-drop in the create workspace modal
- Full transaction wrapping for atomic imports

Create Workspace Modal:
- Extracted from sidebar dropdown into a proper centered modal
  (sidebar's CSS transform was trapping fixed-position elements)
- Rendered at root layout level via uiStore flag
- Create and Import tabs with template picker and file drop zone
2026-03-28 00:25:04 +00:00
xarmian a6d08fdd7e Add Quick Actions — contextual prompt buttons that copy agent commands to clipboard
New QuickAction type in collection settings lets users define prompt templates
with variables ({ref}, {title}, {status}, etc.) that resolve at click time.
Lightning bolt menu appears on item detail and collection pages. Includes
default actions for Tasks, Ideas, Phases, and Docs collections. Fully
customizable via new Quick Actions tab in EditCollectionModal.
2026-03-27 01:56:31 +00:00
xarmian 203080fde1 Implement mutable collection schemas with field editing and option rename migration
Redesigned the EditCollectionModal to support full schema evolution: editable
field labels, delete fields, reorder with up/down buttons, and per-option
chip editing for select/multi_select fields. When select options are renamed,
a bulk migration updates all existing items via SQLite json_set/json_extract.
2026-03-26 23:00:23 +00:00
xarmian 81579847c6 Initial release
Pad — project management for developers and AI agents.
Single Go binary with embedded SvelteKit web UI, SQLite storage,
CLI, and Claude Code /pad skill integration.

https://getpad.dev
2026-03-26 01:52:36 +00:00