Commit Graph

259 Commits

Author SHA1 Message Date
xarmian 9616374a80 feat: upgrade CommandPalette with filters, grouping, and pagination (#126)
* feat: upgrade CommandPalette with filters, grouping, and pagination

Rewrite the Cmd+K search modal as a full-featured search experience:

- Filter chips: collection and status filters from facets, toggle on click
- Grouped results: items grouped by collection with section headers
- Better result cards: ref, title, priority dot, status badge, relative date
- Pagination: "Load more" button appends next page of results
- Recent searches: last 10 queries persisted in localStorage
- Result count displayed below filter chips

* fix: reset loading state on empty query and guard stale loadMore

- Clear loading flag when query is emptied so spinner doesn't stick
- Capture query/filter snapshot before loadMore API call and discard
  the response if they changed while loading

Addresses codex review on PR #126.
2026-04-15 08:10:04 -04:00
xarmian 8351b8194f feat: add faceted counts to search results (#125)
* feat: add faceted counts to search results

Add collection and status faceted counts to SearchResponse so the
frontend can show breakdowns like "Tasks (24) · Ideas (4)". Facets
reflect the full unpaginated result set via two GROUP BY queries
using the same filters as the main search.

- Add SearchFacets type with collections and statuses maps
- Add searchFacets() method using appendSearchFilters for consistency
- Add SearchFacets TypeScript type to frontend
- Add TestSearchFacets covering counts and pagination independence

* fix: include ref-hit items in facet counts

Ref hits (e.g. searching "TASK-42") bypass FTS and wouldn't appear
in FTS-based facet aggregation. Merge them into facets after the
facet queries run so collection/status counts include all results.

Addresses codex review on PR #125.

* fix: remove ref-hit facet merge to avoid double-counting

Unconditionally adding ref hits to facets overcounts when the item
is also found by FTS (the common case). Since we can't cheaply
detect overlap, leave facets as FTS-only. Ref searches typically
return 1 exact match, so the off-by-one is acceptable.

Addresses codex review on PR #125.
2026-04-15 07:37:07 -04:00
xarmian 3e51bfa541 fix: rewrite search ref-hit pagination for correctness (#124)
* fix: rewrite search ref-hit pagination for correctness

The previous ref-hit pagination logic had cascading issues: clearing
results on offset>0 broke the seen map, total was wrong for ref
queries on later pages, and multi-ref hits were dropped entirely.

Rewrite the approach:
- Save ref hits and their IDs before FTS query runs
- Ref hits always appear on page 0; FTS limit reduced accordingly
- On pages after 0, ref hits excluded and FTS offset adjusted
- Track FTS deduplication to correct total (avoid double-counting)
- Total is always >= actual result count as a safety floor
- All ORDER BY clauses include i.id tie-breaker for stable pagination

Addresses all 6 codex review comments on PR #123.

* fix: simplify ref-hit total calculation

Remove the refCount add / ftsDeduped subtract dance which was
inherently broken across pages. Instead, use a simple floor:
total is always at least len(results). The FTS count is accurate
for FTS results; ref-only hits (rare) just bump the floor.

Addresses codex review on PR #124.
2026-04-15 00:04:51 -04:00
xarmian 999bd3cfca feat: add pagination and sorting to search API (#123)
* feat: add pagination and sorting to search API

Extend the search endpoint with limit/offset pagination and sort options.
The response now includes total count (from a separate count query) so
frontends can paginate properly.

- Add Limit, Offset, Sort, Order to SearchParams with Normalize() defaults
- Return SearchResponse struct with total/limit/offset metadata
- Count query runs alongside results query for accurate totals
- Sort options: relevance (default), created_at, updated_at, title
- Add --sort, --limit, --offset flags to CLI search command
- Update frontend SearchFilters and SearchResponse types
- Add TestSearchPagination and TestSearchSorting integration tests

* fix: count ref hits in search totals and handle empty pages

- Ensure total is never less than actual results when direct ref
  matches (e.g. "TASK-5") aren't captured by the FTS count query
- Handle empty page in CLI output: show "No results on this page"
  instead of an invalid descending range like "Showing 11-10 of 5"

Addresses codex review on PR #123.

* fix: paginate ref hits correctly and add sort tie-breaker

- Ref hits now occupy slots on page 0 only; FTS limit/offset adjusted
  so combined results respect the requested pagination contract
- On subsequent pages, ref hits are excluded (already shown on page 0)
- Add i.id as deterministic tie-breaker to all ORDER BY clauses to
  prevent duplicate/missing items across paginated pages

Addresses codex review on PR #123.
2026-04-14 23:40:23 -04:00
xarmian aef0e2326a feat: add collection and field filtering to search API (#122)
* feat: add collection and field filtering to search API

Extend the /search endpoint to support scoping by collection slug and
filtering by structured field values (status, priority, and generic
field.* params). Works on both SQLite FTS5 and PostgreSQL tsvector.

- Add Collection and FieldFilters to SearchParams (store layer)
- Parse collection, status, priority, field.* query params (handler)
- Add SearchFilters type and update api.search() signature (frontend)
- Add --collection, --status, --priority flags to CLI search command
- Add integration tests for collection, field, and combined filtering

* fix: validate field filter keys to prevent SQL injection

Reject field filter keys containing special characters before they
reach JSONExtractText, which interpolates keys directly into SQL.
Keys must match ^[a-zA-Z][a-zA-Z0-9_-]*$ — validation is applied
in both the handler and the store layer as defense in depth.

Addresses codex review on PR #122.
2026-04-14 22:31:18 -04:00
xarmian c5183d2a4b feat: add SSE events for item star/unstar (#121)
* feat: add SSE events for item star/unstar

Emit real-time events for multi-tab sync (PLAN-564, TASK-571):

- New event types: item_starred, item_unstarred
- Emitted from handleStarItem and handleUnstarItem after success
- Includes item ID, title, collection, actor, and source
- Follows existing publishItemEventWithName pattern

* fix: scope star/unstar SSE events to the acting user

Star events are user-specific state, not workspace-wide. Changes:

- Add UserID field to Event struct for user-scoped events
- SSE handler filters events with UserID, only delivering them to
  the user who triggered the action (multi-tab sync without leaking
  star actions to other workspace members)
- Star/unstar handlers set UserID when publishing events
2026-04-14 21:03:37 -04:00
xarmian 9072e49b17 feat: add CLI commands for item starring (#120)
Add star/unstar/starred CLI commands (PLAN-564, TASK-570):

- pad item star <ref> — star an item
- pad item unstar <ref> — unstar an item
- pad item starred [--all] [--format json] — list starred items

Client methods: StarItem, UnstarItem, ListStarredItems.
2026-04-14 20:51:03 -04:00
xarmian f7d93d878d feat: add starred items to dashboard (#119)
* feat: add starred items to dashboard

Add starred items section to the dashboard (PLAN-564, TASK-569):

- Dashboard API: fetches non-terminal starred items for the current user,
  applies RBAC visibility filtering, returns as starred_items array
- TypeScript types: add starred_items to DashboardResponse
- Dashboard UI: renders starred items section with card grid, item refs,
  status pills, and "View all" link to /starred page

* fix: cap starred items in dashboard response to 10

Match the same limit applied to active_items, preventing large payloads
on the polling dashboard endpoint for users with many starred items.
2026-04-14 20:31:37 -04:00
xarmian 77d3fe2d72 feat: add Starred sidebar entry and starred items page (#118)
* feat: add Starred sidebar entry and starred items page

Add dedicated starred items view (PLAN-564, TASK-568):

- Sidebar: new " Starred" link below Activity, with active state
- Starred page: shows user's starred items grouped by collection
- "Show completed" toggle to include/exclude terminal status items
- Loading skeleton, empty state with usage instructions
- Excludes "starred" and "roles" from collection slug detection

* fix: reactively remove unstarred items, guard against stale responses

Two fixes for the starred page:

1. Items list is now derived from starredStore.isStarred, so unstarring
   an item via the ItemCard toggle immediately removes it from the page
   without a refetch.

2. Request sequencing via loadSeq counter prevents stale responses from
   overwriting the UI when rapidly toggling "Show completed".

* fix: reserve collection slugs that collide with workspace UI routes

Prevent collections from being created or renamed to slugs that shadow
workspace-level routes (settings, activity, roles, starred, library,
new). If a reserved slug is generated, "-collection" is appended
(e.g. "starred" becomes "starred-collection").

Also fixes starred page: items list is now reactive to unstar actions,
and loadStarred uses request sequencing to prevent stale responses.

* fix: skip store filter on starred page until store is loaded

Trust the API response when starredStore hasn't loaded yet, since
/starred only returns starred items. Apply the reactive filter only
after the store is loaded, so unstar actions still remove items
immediately but initial render isn't broken by async timing.
2026-04-14 20:15:49 -04:00
xarmian 5d23791c1d feat: add star toggle to web UI item views (#117)
* feat: add star toggle to web UI item views

Add per-user item starring to the web UI (PLAN-564, TASK-567):

- API client: star(), unstar(), starStatus(), starred() methods
- Starred store: loads starred IDs on workspace init, optimistic toggle
- ItemCard: star button (☆/★) in top-left, hidden until hover, always
  visible when starred, amber color
- Item detail page: star button in meta-actions header row
- Workspace layout: loads starred store on workspace change

* fix: guard starred store against stale workspace responses

Add a monotonic request counter so that if a user switches workspaces
quickly, an older in-flight response won't overwrite the current
workspace's starred state. Also guards toggle revert against workspace
changes.

* fix: merge in-flight toggles with load result in starred store

Track toggles that occur while the initial load is in-flight via a
pendingToggles map. When the load completes, merge local mutations
on top of the server response so optimistic updates aren't overwritten.
Reverts also update the pending map for consistency.

* fix: preserve toggles on load error, serialize per-item toggles

Two fixes for starred store edge cases:

1. Error path now applies pendingToggles instead of resetting to empty,
   so optimistic toggles survive a failed initial load.

2. Per-item toggle lock (toggleInFlight set) drops rapid duplicate
   clicks while a toggle API call is in flight, preventing out-of-order
   requests from producing inconsistent state.

* fix: clear stale stars immediately on workspace load

Reset starredIds at the start of load() before the async fetch, so a
previous user/workspace's stars are never briefly visible during SPA
navigation or re-authentication flows.
2026-04-14 19:27:52 -04:00
xarmian 844e40f0a9 feat: add star/unstar API endpoints (#116)
* feat: add star/unstar API endpoints

Add REST API for item starring (PLAN-564, TASK-566):

- POST /workspaces/{ws}/items/{slug}/star — star item (idempotent, 204)
- DELETE /workspaces/{ws}/items/{slug}/star — unstar item (204 or 404)
- GET /workspaces/{ws}/items/{slug}/star — check star status ({"starred": bool})
- GET /workspaces/{ws}/starred — list starred items (?include_terminal=true)

All endpoints are scoped to the authenticated user, check item visibility
via RBAC/grants, and enrich list responses with parent links and refs.

* fix: enforce RBAC visibility filtering on starred items list

Apply the same collection/item grant filtering used by handleListItems
to handleListStarredItems. Without this, guests or restricted members
could see starred items from collections they no longer have access to.
2026-04-14 18:15:19 -04:00
xarmian d372be6aff feat: add item_stars table and store methods (#115)
* feat: add item_stars table and store methods for per-user item starring

Add the data layer for item starring/favorites (PLAN-564, TASK-565):

- Migration 042 (SQLite) / 022 (PostgreSQL): item_stars join table with
  (user_id, item_id) primary key, ON DELETE CASCADE, and indexes
- Store methods: StarItem, UnstarItem, IsItemStarred, AreItemsStarred
  (batch), ListStarredItems (enriched), CountStarredItems, DeleteStarsForItem
- 8 tests covering CRUD, idempotency, per-user isolation, and batch ops

* fix: implement includeTerminal filter in ListStarredItems

The includeTerminal parameter was accepted but unused — starred items
in terminal statuses (done, completed, etc.) were always returned.
Now post-filters using IsTerminalStatusDefault, matching the pattern
used by GetRoleBoardItems. Adds test coverage for the filter.

* fix: use per-collection schemas for terminal filtering, cascade user deletes

Addresses two code review findings:

1. Terminal filtering now loads collection schemas and uses
   IsTerminalStatus per collection instead of IsTerminalStatusDefault.
   This correctly handles custom terminal statuses (e.g. "closed").

2. Added ON DELETE CASCADE to the user_id foreign key in both SQLite
   and PostgreSQL migrations, so deleting a user automatically cleans
   up their stars.

* perf: use lightweight query for collection schema loading

Replace ListCollections call in buildCollectionSchemaMap with a direct
SELECT of only id and schema columns. ListCollections runs per-collection
COUNT(*) queries for ActiveItemCount which are unnecessary here.
2026-04-14 17:50:48 -04:00
xarmian 6a552b83ae fix: link comment to activity record to prevent duplicate timeline entries (#114)
* fix: link comment to activity record to prevent duplicate timeline entries (#BUG-563)

When creating a comment, the handler created a comment (activity_id=NULL) and a
separate "commented" activity but never linked them. The timeline dedup logic only
filters activities that have a linked comment, so both showed up as separate entries.

Fix: create the activity first via logActivityWithMetaReturningID, then pass its ID
to CreateComment so buildTimeline correctly deduplicates them.

* fix: only link activity ID to comment when activity insert succeeds

Address review feedback: CreateActivity assigns an ID before Exec and
returns it even on insert failure. Since comments.activity_id has a FK
constraint, setting a dangling reference would break comment creation.
Now we only set ActivityID when the activity was actually persisted.
2026-04-14 16:21:21 -04:00
xarmian b620b7e5cc feat: add edit button to collection detail page header (#113)
* feat: add edit button to collection detail page header

Wire up the existing EditCollectionModal to the collection detail page
with a pencil icon button in the header actions bar. Owner-only,
responsive (icon-only on mobile), refreshes page data after saves.

Closes IDEA-494

* fix: navigate to new slug after collection rename in edit modal

When a collection is renamed, the backend regenerates the slug. The
onupdated callback now receives the updated Collection object, so the
collection page can detect a slug change and navigate to the new URL
instead of 404ing on the old slug.

Addresses PR review feedback from #113.

* fix: refresh collectionStore after edit modal update

Ensures the sidebar reflects updated collection name/slug/icon
immediately after editing, matching the settings page behavior.

Addresses P2 review feedback from #113.
2026-04-14 14:37:45 -04:00
xarmian aeda627320 fix: sidebar collection drag-and-drop only moving by one slot (#111)
Fixed race condition where $effect reset sidebarCollections mid-update
because isDraggingSidebar was cleared before API calls completed.

- Capture reordered array locally; keep drag flag true during updates
- Fire all sort_order updates in parallel with Promise.all
- Wrap in try/finally so drag state always resets on failure
- Guard reset with generation counter to prevent stale finalize from
  clobbering a newer drag operation

Fixes BUG-562
2026-04-14 11:19:06 -04:00
xarmian 75bed01701 feat: sticky breadcrumb header with copy item ID button (#112)
* feat: sticky breadcrumb header with copy item ID button

Make the breadcrumb navigation bar sticky so it stays visible when
scrolling on item detail pages. Add a copy-to-clipboard button next
to the item ref (e.g. TASK-5) that shows a "Copied!" tooltip on click.
Uses the existing copyToClipboard utility with non-SSL fallback.

Closes IDEA-501

* fix: lower sticky breadcrumb z-index below mobile header

The mobile layout has a sticky header at z-index 5. Lower the
breadcrumb's z-index to 4 so it slides under the mobile header
instead of overlapping navigation controls.

* fix: offset sticky breadcrumb below mobile header instead of hiding it

Instead of lowering z-index (which hides the breadcrumb behind the
mobile header), offset it with top: 45px on mobile so it stacks
neatly below the mobile nav. Keeps z-index: 10 so the sticky
effect works properly on all screen sizes.
2026-04-14 10:06:27 -04:00
xarmian 1fb50d4f62 feat: hide plan settings in non-cloud mode, improve overrides UX (#110)
* feat: hide plan settings in non-cloud mode, improve plan overrides UX

1. Gate Plan column, Plan dropdown, and Plan Overrides behind cloud_mode
   so self-hosted instances don't see irrelevant billing UI.

2. Replace raw JSON textarea for plan overrides with a structured grid
   of labeled number inputs for each known limit (workspaces, items per
   workspace, members, API tokens, webhooks). Each field shows "default"
   as placeholder and accepts -1 for unlimited.

Addresses IDEA-561

* fix: address codex review — preserve extra overrides, fix colspan, validate integers

- Preserve non-modeled override keys (e.g. storage_bytes) by stashing
  them on select and merging them back on save
- Fix colspan: 6 columns in cloud mode, 5 without (was off by one)
- Use Number() + Number.isInteger() instead of parseInt to reject
  floats and scientific notation instead of silently truncating
2026-04-14 09:22:08 -04:00
xarmian ba01d95111 feat: add web UI for TOTP 2FA setup in user settings (#109)
* feat: add web UI for TOTP 2FA setup in user settings

Add a Two-Factor Authentication section to the console settings page
so users can enable/disable TOTP 2FA from the browser. The backend
API already existed (PR #77); this wires up the frontend.

- Add 2FA section to console settings with enable/disable flows
- Enable flow: QR code + manual secret + verification code input
- Recovery codes displayed with copy/download after setup
- Disable flow: password confirmation modal
- Add totp.setup/verify/disable methods to API client
- Add TOTP types (TOTPSetupResponse, TOTPVerifyResponse, etc.)
- Add totp_enabled to User type and /auth/me response
- Add qrcode npm dependency for rendering otpauth:// URIs

Closes TASK-402

* fix: address codex review — separate QR rendering from setup, use clipboard util

- Separate QR code rendering from TOTP setup API call so a QR failure
  doesn't abort setup when manual entry is still available
- Use existing copyToClipboard utility with legacy fallback instead of
  raw navigator.clipboard.writeText
2026-04-14 09:04:37 -04:00
xarmian 3d284641d7 feat: add audit log UI for admin console (#108)
* feat: add audit log UI for admin console

Replace placeholder with full audit log page: filterable by action type
and date range, paginated table with relative timestamps, color-coded
action badges, parsed metadata details, and IP addresses.

* fix: show Unknown for missing actors, guard against stale filter responses

Show "Unknown" instead of "System" for entries without actor info (e.g.
failed logins). Add a request counter so rapid filter changes discard
stale responses instead of overwriting newer results.
2026-04-14 07:05:49 -04:00
xarmian 56adba4b58 feat: add invitation management panel for admin console (#107)
* feat: add invitation management panel for admin console

Platform-wide view of all pending invitations with search, resend, and
revoke. Resend creates a fresh invitation code and sends the email.
New admin endpoints: GET/POST/DELETE for /admin/invitations.

* fix: check email opt-out on resend, abort on stale delete, reload list

Respect unsubscribe preferences before resending invitation emails.
Abort resend if the old invitation was already accepted/revoked
concurrently. Reload the full invitations list after resend since the
row ID changes.
2026-04-13 23:02:19 -04:00
xarmian 86451174ad feat: add user detail panel with workspace memberships (#106)
* feat: add user detail panel with workspace memberships

New GET /api/v1/admin/users/{id}/workspaces endpoint returning workspace
name, slug, role, and join date. Frontend loads memberships when a user
row is expanded and displays them as a linked list with role badges.

* fix: scope workspace fetch error/loading to active selection

Gate both the catch and finally blocks with a selectedId check so stale
requests from previously selected users don't wipe workspace data or
clear the loading indicator for the current selection.
2026-04-13 22:36:14 -04:00
xarmian b3af1acd07 feat: add last active tracking for users (#105)
* feat: add last active tracking for users

Track when users were last active via a throttled update (once per 5
minutes) in the auth middleware. Adds last_active_at column, displays
relative time in admin user list with full timestamp on hover.

* fix: bound last-active goroutine with 3s context timeout

Use a short-lived context for the background TouchUserActivity write
so it gets cancelled under DB pressure, preventing goroutine/connection
buildup from unbounded background work.
2026-04-13 22:19:41 -04:00
xarmian d968b551b7 feat: add account disable/deactivation (#104)
* feat: add account disable/deactivation for admin users

Allow admins to soft-disable user accounts without deleting data.
Disabled users get a 403 on all authenticated requests, their sessions
are invalidated on disable, and they show as visually dimmed with a
red "disabled" badge in the admin console. Includes migration for
disabled_at column, auth middleware check, disable/enable endpoints
with audit logging, and frontend toggle with confirmation dialog.

* refactor: auto-discover migrations from embedded filesystem

Replace hardcoded migration lists with fs.ReadDir on the embedded FS
directories. New migrations are now picked up automatically by filename
sort order — no need to manually register them in store.go.

* fix: block disabled users at login and capture IDs before async calls

Reject disabled accounts in the login handler before session creation,
not just in RequireAuth middleware (which exempts auth routes). Also
capture selectedId into a local const in all async admin panel functions
to prevent stale updates if the selection changes during a request.

* fix: enforce disabled check in OAuth and password reset flows, always invalidate sessions

Block disabled users in all session-minting paths (OAuth login, password
reset) not just password login. Also remove early return for
already-disabled users in the disable endpoint so session invalidation
always runs, handling retry after partial failure.
2026-04-13 21:56:40 -04:00
xarmian 79d7d26a00 feat: add admin password reset for other users (#103)
* feat: add admin password reset for other users

New POST /api/v1/admin/users/{id}/reset-password endpoint. When email is
configured, sends a password reset link. Otherwise generates a temporary
password and invalidates existing sessions. Includes audit logging via
new password_reset_by_admin action and frontend UI with confirmation.

* fix: treat session revocation and email send as hard failures

Make session invalidation failure abort the reset instead of silently
continuing, and send the reset email synchronously so delivery failures
are surfaced to the admin caller.
2026-04-13 20:59:35 -04:00
xarmian f97ab766f5 feat: add admin role management (promote/demote users) (#102)
* feat: add admin role management (promote/demote users)

Allow admins to change user roles between admin and member from the
admin console. Includes safety guards to prevent self-demotion and
demoting the last admin, with full audit logging.

* fix: make last-admin demotion guard atomic

Move the admin count check into the SQL UPDATE itself so two concurrent
demotion requests cannot both observe >1 admin and proceed. The
conditional UPDATE only demotes when at least one other admin exists,
eliminating the TOCTOU race.
2026-04-13 20:12:16 -04:00
xarmian a83da0e241 feat: refactor admin console into tabbed layout (#101)
* feat: refactor admin console into tabbed layout

Split the monolithic admin page into a tabbed layout with sub-pages:
- Users tab (default) — user list, search, plan editing
- Settings tab — email configuration and plan limits
- Invitations tab (placeholder for TASK-559)
- Audit Log tab (placeholder for TASK-560)

Extract shared admin utilities (adminFetch, adminPatch, adminPost,
types, reactive stats store) into lib/stores/admin.svelte.ts.

Part of PLAN-552: Admin Console Enhancement (IDEA-242)

* fix: show error state when admin users API fails

Instead of silently swallowing fetch errors and showing an empty table,
display an error message with a retry button. Addresses PR feedback.
2026-04-13 17:58:46 -04:00
xarmian f276745478 fix: sidebar collection counts ignore terminal status settings (#100)
When all items in a collection had terminal statuses (e.g. all bugs
"fixed"), the sidebar showed the total item count instead of 0.

Root cause: ActiveItemCount used `json:"omitempty"`, so a zero value
was omitted from the API response. The sidebar fallback logic then
displayed item_count (total) instead. Additionally, ListCollections
used a hardcoded global terminal status list instead of respecting
each collection's configured terminal_options.

- Remove omitempty from ItemCount/ActiveItemCount so 0 serializes
- Compute active counts per-collection using schema terminal_options
- Show count of 0 in sidebar when collection has items but all are done
2026-04-13 16:47:44 -04:00
xarmian 2e03222aa5 feat: restore pad init as smart multi-step entry point (#99)
* feat: restore `pad init` as smart multi-step entry point

Adds a top-level `pad init` command that detects the current state and
walks through each setup step as needed: configure connection, start
server, bootstrap admin account, authenticate, create/link workspace,
and install/update AI skill files.

Safe to re-run anytime — skips completed steps and shows a status
summary when everything is healthy.

Also refactors `pad workspace init` to use shared helpers and adds a
hint pointing users to `pad init` when prerequisites are missing.

Ref: IDEA-499, PLAN-546

* fix: validate --template flag before workspace creation in pad init

Adds the same preflight template validation that workspace init has,
preventing a partially initialized workspace from being created when
an invalid template name is passed.

Ref: PR #99 review feedback
2026-04-13 15:01:23 -04:00
xarmian ed87d0d676 Merge pull request #98 from xarmian/fix/db-commands-sqlite-support
fix: make pad db backup/restore work with SQLite
2026-04-13 10:39:23 -04:00
xarmian 40e6a8b705 fix: make pad db backup and pad db restore work with SQLite
The db commands were PostgreSQL-only, which made them useless for
self-hosted users on the default SQLite setup. Now both commands
auto-detect the database driver and do the right thing:

- SQLite (default): file copy of ~/.pad/pad.db with WAL/SHM handling
- PostgreSQL: existing pg_dump/psql behavior (unchanged)
2026-04-13 14:39:11 +00:00
xarmian 7ca0463e70 feat: browser-based CLI authentication flow (#97)
Replace the email/password terminal prompt in `pad auth login` with a
browser-based auth flow. The CLI creates a pending session, prints a URL
the user opens in their browser (works for localhost, remote VPS, or
Pad Cloud), and polls until the session is approved.

- Add CLI auth session endpoints (create, poll, approve)
- Add browser approval page at /auth/cli/{code}
- Rewrite `pad auth login` to use browser flow by default
- Keep `pad auth login --interactive` as email/password fallback
- Add login page redirect param support for post-login bounce-back
- Add SQLite and PostgreSQL migrations for cli_auth_sessions table

Closes PLAN-539, IDEA-404
2026-04-13 10:11:16 -04:00
xarmian 1ba9c91992 feat: email unsubscribe for non-transactional emails (#96)
* feat: email unsubscribe for non-transactional emails

Add CAN-SPAM compliant unsubscribe support:

- New email_optouts table (by email address, not user ID) so
  uninvited recipients can opt out without an account
- HMAC-signed unsubscribe tokens (derived from Maileroo API key)
  so links work without authentication
- GET /api/v1/unsubscribe endpoint with simple HTML confirmation page
- Invitation emails now include unsubscribe footer link
- Welcome emails accept unsubscribe URL parameter
- Before sending invitation emails, check opt-out table and silently
  skip opted-out addresses (prevents invite spam)
- Password reset emails are exempt (transactional, user-initiated)

Fixes BUG-256.

* fix: hide "Copy invite link" when code is unrecoverable

For hashed invitations the plaintext code can't be recovered, so the
button was copying a broken URL. Now shows "Sent via email" label
instead. Only shows the copy button when join_url or code is available.

Fixes BUG-255.
2026-04-13 09:14:04 -04:00
xarmian 26af891432 fix: dashboard "New Idea" button now targets first collection (#95)
The dashboard had two buttons that both called requestQuickAdd() with
no argument, so both created tasks. Now:

- "New Task" explicitly targets the tasks collection
- The second button dynamically targets the first non-system, non-task
  collection by sort order (showing its icon and name)
- requestQuickAdd() accepts an optional collection slug, which the
  sidebar respects when choosing the target collection

Fixes BUG-498.
2026-04-12 23:54:00 -04:00
xarmian ac24fb742c fix: breadcrumbs show parent item path for child items (#94)
When viewing a child item (e.g. TASK-101 under PLAN-10), the breadcrumb
now shows "Home / Plans / PLAN-10 / TASK-101" instead of the flat
"Home / Tasks / TASK-101".

- Add parent_slug and parent_collection_slug fields to Go Item model
- Populate them in both single-item and bulk enrichment paths
- Add corresponding TypeScript types
- Update breadcrumb nav to show parent collection and parent item
  when the item has a parent, falling back to the item's own collection

Fixes BUG-516.
2026-04-12 23:53:58 -04:00
xarmian e49a020fc5 fix: mobile UI — sidebar buttons, avatar, share copy (#93)
* fix: mobile UI bugs — sidebar buttons, avatar, share link copy

- Show sidebar + buttons on touch devices using @media (hover: none)
  instead of requiring hover (BUG-537)
- Add user avatar and menu to mobile TopBar header, filling the blank
  space next to the workspace selector (BUG-536)
- Wire ShareDialog copy into existing clipboard fallback utility so
  share link copy works over HTTP (BUG-513)

* fix(mobile): remove extra right padding on mobile topbar

The .topbar has 72px right padding to clear space for the absolutely-
positioned desktop avatar. On mobile the avatar is in the normal flex
flow, so that padding created a blank gap. Override to var(--space-3).
2026-04-12 23:41:35 -04:00
xarmian 1e464ffdac fix: apostrophe in slugs, split auto-close, and move navigation (#92)
- Strip apostrophes in slugify() so "Dave's Workspace" becomes
  "daves-workspace" instead of "dave-s-workspace" (BUG-517)
- Use replaceState when navigating after item move to avoid polluting
  browser history (BUG-538)
- Don't auto-close items when split children are done — splitting work
  out doesn't mean the original is complete (BUG-401)
2026-04-12 23:31:13 -04:00
xarmian 56ce1e966d Merge pull request #91 from xarmian/feat/console-navigation
feat: console navigation, PostgreSQL CI, and operational improvements
2026-04-12 21:43:01 -04:00
xarmian da2997c564 fix: check HTTP status in admin settings save (PR #91)
savePlatformSettings used raw fetch which doesn't throw on 4xx/5xx,
so failed saves (CSRF rejection, auth errors) silently showed "Saved".
Now checks resp.ok before reporting success.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-04-13 01:41:41 +00:00
xarmian b027046605 fix: address review findings for PR #91 (iteration 1)
Ensure make test-pg cleans up Docker containers even when tests fail
by capturing the exit code and running cleanup unconditionally. Remove
dead CSS rules from root page after welcome template simplification.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-04-13 01:32:12 +00:00
xarmian b2b4feecb9 feat: console navigation, PostgreSQL CI, and operational improvements
- Route root (/) to /console for centralized workspace management
- Update TopBar user dropdown with console nav links (workspaces, settings, billing, admin)
- Move account settings (profile, password, tokens) from workspace settings to /console/settings
- Enhance admin page with email configuration UI and CSRF-protected writes
- Add PostgreSQL CI job to GitHub Actions with race detector on main
- Add `make test-pg` for local PostgreSQL testing via docker-compose
- Expand health/ready endpoint with DB connection pool stats
- Increase item number retry limit for high-concurrency environments
- Add concurrent store benchmarks and FTS search quality tests
- Add AGENTS.md for multi-agent development guidance
2026-04-13 01:29:15 +00:00
xarmian ab911ef916 Merge pull request #90 from xarmian/feat/cloud-hardening-plan-503
Cloud hardening and security follow-ups (PLAN-503)
2026-04-12 21:28:37 -04:00
xarmian b7808f12a1 fix: address Codex review findings for PR #90 (iteration 2)
Update admin frontend to handle new paginated user list response shape
({ users, total } instead of bare array). Add legacy pad_session cookie
fallback to SessionAuth middleware matching validateSessionCookie. Exempt
/api/v1/plan-limits from RequireAuth so billing page can read limits
without authentication.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-04-13 01:27:51 +00:00
xarmian 460d213526 fix: address review findings for PR #90 (iteration 1)
Exempt new sidecar endpoints (/admin/stripe-customer-id, /admin/user-by-customer)
from RequireAuth and CSRF middleware. Fix OAuth unlink lockout guard that never
triggered because PasswordHash is always non-empty. Return total count from
admin user list for pagination support.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-04-13 01:11:04 +00:00
xarmian 93b08b3e1b fix: allow bootstrap in cloud mode when no users exist yet 2026-04-13 01:03:12 +00:00
xarmian a0b8abd4da fix: Dockerfile CMD needs 'server start' (server is a subcommand group) 2026-04-13 01:03:12 +00:00
xarmian 8095885cf2 fix: Dockerfile CMD should be 'server' not 'serve' 2026-04-13 01:03:12 +00:00
xarmian 92580905bb feat: cloud hardening and security follow-ups (PLAN-503)
Address 11 issues identified during the PLAN-427 security review:

Critical/High:
- Stripe customer-to-user mapping with indexed lookup (TASK-505)
- OAuth provider linking with explicit consent model (TASK-504)
- CSRF tokens on admin console mutations (TASK-506)
- Rate limiting on cloud admin and OAuth endpoints (TASK-507)

Medium:
- __Host- cookie prefix for subdomain protection (TASK-510)
- Billing portal verifies customer ownership server-side (TASK-515)
- Transactional account deletion with rollback (TASK-509)
- Streaming data export with 60s timeout (TASK-508)
- Migration registration for new columns (TASK-514)

Low:
- Billing page fetches actual plan limits from API (TASK-511)
- Admin user search/filter pushed into SQL with pagination (TASK-512)
2026-04-13 01:03:12 +00:00
xarmian 9220d3bb53 Merge pull request #89 from xarmian/feat/cloud-infrastructure
feat: cloud infrastructure for hosted Pad (PLAN-427)
2026-04-12 20:56:37 -04:00
xarmian e6f123a4c3 fix: address Codex review findings for PR #89 (iteration 2)
- Exempt /admin/plan from RequireAuth and CSRF middleware so the
  pad-cloud sidecar can call it with cloud_secret body auth
- Add X-CSRF-Token header to admin console PATCH requests
- Send plan_overrides as a JSON string (not parsed object) to match
  backend *string decoder expectation
- Restrict confirm-only account deletion to cloud mode to prevent
  password users from bypassing re-auth

Co-Authored-By: Claude <noreply@anthropic.com>
2026-04-13 00:54:21 +00:00
xarmian b2ec0a4f55 fix: address review findings for PR #89 (iteration 1)
Fix admin limits endpoint returning wrong defaults for pro plan, correct
swapped billing page usage numbers, validate expires_at format in plan
endpoint, and handle errors properly in admin stats endpoint.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-04-13 00:24:36 +00:00