* feat: independent board scrolling, unified card style, lane reordering, and new-item modal
- BoardView: switch from CSS grid to flex layout with independent per-column
scrolling, matching the Roles board UX
- ItemCard: redesign to match Roles board card style — top row with optional
collection badge + ref, compact meta row with colored status/priority text
- Roles board: replace inline card markup with shared ItemCard component,
add HTML5 drag-and-drop lane reordering (persisted via new API endpoint),
rename "Highlight Mine" to "Mine", add "+ New" button with collection
picker modal
- Backend: add PUT /roles/board/lane-order endpoint and UpdateAgentRoleOrder
store method for batch role sort_order updates
- Collection page: board view now fills viewport height so columns have
bounded scroll areas
* fix: resolve svelte-check type error and remove unused CSS selectors
- Add null guard on lane.role in openEditModal onclick
- Remove unused .role-edit-actions, .role-btn-create, .role-btn-cancel CSS
* fix: correct lane reorder insert index when dragging forward
After splicing out the source lane, downstream indices shift left by one.
Adjust the insert index when srcIdx < dstIdx to place the lane at the
correct drop target position.
The early return for same-lane drops (oldKey === key) was skipping
the sort persistence code entirely. Restructured handleDndFinalize
so the cross-lane role update is a nested conditional, and the sort
order persistence always runs regardless of whether a cross-lane
move occurred.
The $effect that syncs laneData from orderedLanes was firing when
isDragging was set to false, overwriting the optimistic sort order
with stale data. Now isDragging stays true until after lanes state
is updated with the new sort order, so the $effect sees correct data.
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)
Assigned user names were shown both in the lane header as pills and
on individual item cards. Remove the lane header pills — the card
already shows the assigned user, and duplicating it in the header
added clutter without information.
- Dialog uses fixed positioning with translate(-50%, -50%) for
reliable viewport centering on all screen sizes
- Add Role column no longer stretches to full lane width/height —
sized to its content with align-self: flex-start
Remove the ⚙ Manage button and its full-list modal. Instead:
- Each role lane header has a ✎ edit button (appears on hover)
that opens a focused dialog for that role's fields + delete
- A dashed "+" column at the far right of the board opens the
same dialog in create mode
- Empty state has a "Create your first role" CTA
- Dialog is simpler: one role at a time with name, icon,
description, tools fields, save/cancel/delete
Replace the loadData() round-trip after dropping with an optimistic
local update. The item moves instantly in lanes state with updated
role fields, and the API call fires in the background. Only reloads
from server on error (revert). No scroll reset, no flash.
Change lanes-container from align-items: flex-start to stretch so all
lanes match the height of the tallest column. Combined with flex: 1 on
lane-items, the drop target area fills the full remaining lane height.
Keep isDragging true during the API call so the $effect doesn't
revert laneData from stale orderedLanes. The item stays visually
in the target lane while the server processes the role change,
then isDragging releases after loadData() refreshes from the server.
* 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.
* 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.
* 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>
* 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>
* Fix all 17 svelte-check warnings across 7 components
- Add tabindex to toolbar role elements (BoardView, Editor)
- Replace nested buttons with div[role=button] in ListView group headers
- Add role="none" to click-to-close backdrop overlays (Editor, slug page)
- Fix label→span for non-input field labels (CreateWorkspaceModal)
- Add keyboard handlers to interactive divs (CreateWorkspaceModal drop zone, slug page modal)
- Remove unused .slash-backdrop CSS (Editor) and input[type=text] selector (CreateWorkspaceModal)
- Fix state_referenced_locally in RawMarkdownEditor ($state init)
- Add svelte-ignore for conditional tabindex false positive (ToastContainer)
* feat: add build version, commit hash, and timestamp to CLI and web UI
Inject version info via ldflags during build (Makefile, GoReleaser,
Dockerfile). Expose version/commit/build_time in the health API
endpoint and display it in the sidebar footer. Dev builds show
"dev (abc1234 ...)", releases show "v1.2.3 (abc1234 ...)".
* feat: email-based password reset flow (IDEA-81)
Add full password reset flow: forgot password request, time-limited
reset tokens (1hr, single-use, SHA-256 hashed), new password form,
and automatic session creation after reset.
* fix: use all: prefix in go:embed to include _-prefixed files
Go's embed package excludes files starting with _ or . when recursing
directories. SvelteKit/Vite occasionally generates chunk filenames with
_ prefixes (e.g. _VLZtjCJ.js), causing them to be silently dropped
from the embedded filesystem and served as HTML by the SPA fallback.
The all: prefix includes everything regardless of filename prefix.
Fixed in both embed.go and the Makefile which regenerates it.
* fix: address PR review — atomic token consumption, error handling, log reset URL
- Replace ValidatePasswordReset + MarkPasswordResetUsed with atomic
ConsumePasswordReset using UPDATE ... WHERE ... RETURNING to prevent
race conditions where two concurrent requests consume the same token
- Handle DeleteUserSessions errors (log instead of silently ignoring)
- Log the full reset URL when email is not configured so the admin
CLI fallback is actually usable
Replace the single long-scroll settings page with a 4-tab layout:
General (workspace + theme), Members, Collections, and Danger Zone.
Tab selection persists in the URL hash and survives page refreshes.
The meta area (timestamps + action buttons) was a single flex row that
wrapped chaotically on narrow screens. Split it into a `.meta-info` row
for timestamps/save status and a separate `.meta-actions` row for buttons,
both with flex-wrap. Renamed `.history-btn` to `.action-btn` with
consistent sizing (min-width, white-space: nowrap).
Closes IDEA-71
Replace conditional {#if} rendering of save status with always-present
span using opacity transitions. Element stays in the DOM flow so the
flex row never reflows when state changes.
The "History" panel on item detail pages was blank because it only showed
content version snapshots (stored in item_versions). Field changes like
status updates, title edits, and moves generate activity entries but not
content versions, so the panel appeared empty for most items.
Changes:
- Add GET /items/{itemSlug}/activity endpoint to fetch per-item activity
- Update VersionHistory component to fetch both versions AND activity,
merging them into a unified timeline sorted newest-first
- Deduplicate entries where a version and activity share the same timestamp
- Collapse rapid content autosave bursts (within 5 min) to reduce noise
- Content version entries are expandable with diff view and restore
- Activity entries show field changes, status transitions, moves, etc.
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"
Change notification rows with links from <button> with goto() to <a>
tags with href, enabling SvelteKit client-side navigation. Remove the
onclose() call so the notification panel stays open after clicking an
item. Add text-decoration/color overrides so <a> tags match the
existing visual style.
Fixes BUG-9.
* 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)
position: sticky on column headers doesn't work because .board-view
has overflow-x: auto on mobile, creating a separate scroll context.
The sticky made headers float over the mobile navbar instead of
pinning correctly. Reverted to non-sticky headers but kept the
drag handle hidden on mobile (still useful).
Column header sticks to top of viewport when scrolling down through
items on mobile. Page title and filters scroll off naturally, then the
column name (e.g. "Planned (5)") pins at the top so you always know
which column you're in. Also hides drag handle on mobile since column
reordering via drag isn't practical on touch.
- Column width 78vw → 75vw so ~25vw of the next column is visible
- scroll-snap-type mandatory → proximity so the peek persists at rest
- Removed scroll-behavior: smooth for native momentum feel
The previous fix (78vw + mandatory snap) still hid adjacent columns
because mandatory snap forced the viewport to align flush with each
column edge. Proximity snap allows the natural resting position to
show the neighboring column.
Narrower columns (78vw from 85vw) with start-aligned snap and
horizontal padding so users can see adjacent columns peeking in
from the edges — clear visual affordance that horizontal scrolling
is available.
Breadcrumb now reads "Home / Tasks / TASK-5" instead of
"Home / Tasks / Fix the OAuth redirect bug" — much cleaner navigation.
Falls back to title if no item ref exists.
When PAD_PASSWORD is set (env var) or password is configured in
~/.pad/config.toml, the server requires authentication:
Backend:
- SessionManager with HMAC-SHA256 signed cookies (7-day TTL)
- POST /api/v1/auth/login — validates password, sets session cookie
- GET /api/v1/auth/session — returns auth status (exempt from auth)
- POST /api/v1/auth/logout — destroys session, clears cookie
- PasswordAuth middleware gates all API/page requests
- API tokens still work independently (no change to CLI flow)
- Constant-time password comparison + 500ms delay on failure
Frontend:
- Login page at /login with password form and error handling
- Root layout checks auth status before loading app shell
- Global 401 handler in API client redirects to /login
- Login page renders without sidebar/app shell
When no password is configured, everything works exactly as before
(zero-friction localhost). This is a security requirement for any
deployment that exposes the server beyond localhost.
Dashboard activity rows now display change metadata inline (e.g.
"status: open → done") when available. Adds parseActivityChanges
helper and subtle muted styling for the change text.
page.params.workspace is string | undefined, but EmptyState expects
string. Add fallback to empty string so the type narrows correctly.
Fixes CI failure from svelte-check.
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.
Create PhaseChart SVG component showing task completion over time with
an ideal burndown line, actual step chart, and today marker. Wire it
into PhaseTasks (shown when phase has start_date) and pass phaseFields
from the item detail page.
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
Toast notifications from SSE events (agent/CLI item creation) now
include a link to the created item. Clicking the toast navigates
to the item detail page. The notification panel history entries
are also clickable when they have links.
- Toast and HistoryEntry types gain optional `link` field
- ToastContainer renders clickable toasts with hover state and → hint
- NotificationPanel renders linked entries as buttons with hover
- Workspace layout passes item URL to SSE-triggered toasts