mirror of
https://github.com/PerpetualSoftware/pad.git
synced 2026-09-21 10:03:29 +00:00
cf3e64caf4
* feat(web): add legal footer + consent notice on auth pages (TASK-714) Pad Cloud (cloudMode=true) needs visible Terms / Privacy / Sub-processors links so Stripe Live-mode compliance is defensible and users know what they're agreeing to. Self-hosted installs (cloudMode=false) don't need these — the legal docs at getpad.dev are Perpetual Software LLC's TOS for the hosted service, not the user's own instance. Changes: - New LegalFooter.svelte component: renders Terms · Privacy · Sub-processors links to https://getpad.dev/{terms,privacy,subprocessors}. Gated on a cloudMode prop so self-hosted installs see nothing. - login, register, forgot-password pages: render <LegalFooter> below the card. Each page already fetches session; register/forgot-password now persist session.cloud_mode into a local $state so the footer can be reused consistently. - register page adds a "By creating an account, you agree to Terms and Privacy Policy" consent notice directly under the Create account button (only when cloudMode=true). This is the signup touchpoint for Stripe Live mode. - Auth-page containers switched to flex-direction: column so the card and footer stack cleanly centered. Cookie banner intentionally deferred: the privacy policy already asserts "strictly-necessary cookies only, no banner required" and app.getpad.dev only sets first-party session/CSRF cookies. Stripe Checkout runs on billing.stripe.com (separate origin) so its cookies don't apply here. Parent: PLAN-645 (Pad Cloud Beta Readiness). This covers the main launch-blocking legal bullet (Stripe Live mode compliance); the pad-web marketing site already hosts the actual legal content. * fix(web): read cloudMode from authStore; add link affordance (Codex round 1) Addresses PR #229 review findings: MEDIUM — register and forgot-password pages were fetching session via api.auth.session() specifically to derive cloudMode, duplicating the root layout's authStore.load() and adding a silent failure path (if the extra request failed, the legal footer/consent would vanish even on Pad Cloud). Switched to authStore.cloudMode in both pages. register still calls api.auth.session() for its pre-existing setup_required + authenticated checks, but no longer reads cloud_mode from that call. forgot-password no longer needs onMount at all — its only addition was the cloudMode fetch. LOW — legal footer links had no affordance until hover: muted color, no underline, nothing for keyboard users. Added persistent subtle underline (1px, 2px offset) and :focus-visible outline so the links are discoverable for touch and keyboard users. Also: login page intentionally left untouched. It has a pre-existing local cloudMode state used for OAuth buttons + sign-up link + legal footer; unifying it with authStore is a separate refactor and outside this PR's scope. * fix(auth): add authStore.ensureLoaded for post-logout auth nav (Codex round 2) Addresses PR #229 round 2 finding: MEDIUM — After logout, authStore.session is cleared and the root layout doesn't re-run onMount on SPA navigation, so subsequent visits to /register or /forgot-password would read authStore.cloudMode=false and silently hide the legal footer/consent on Pad Cloud. Fix: add authStore.ensureLoaded() which returns the cached session when present or fetches it otherwise. register's onMount now routes its pre-existing session fetch through ensureLoaded (so the same call populates authStore for downstream components like LegalFooter). forgot-password calls ensureLoaded() on mount — cheap no-op when the store is already populated, one fetch when it's been cleared. This keeps the single-source-of-truth benefit from round 1 while handling the logout-then-navigate case Codex flagged. * fix(auth): coalesce concurrent session loads (Codex round 3) Addresses PR #229 round 3 finding: MEDIUM — authStore.ensureLoaded() did a bare 'if (session)' check, so a hard page load that fired both the root layout's authStore.load() and the page's ensureLoaded() could issue two /auth/session requests. If one succeeded and the other failed, the later catch path (session = null) would overwrite the good session, leaving cloudMode=false after a successful fetch. Fix: add an inflight Promise in auth.svelte.ts. load() returns the inflight promise when one exists; the then/catch/finally chain runs exactly once per fetch and clears inflight in finally. ensureLoaded() keeps its cached-session short-circuit and otherwise delegates to load(), so concurrent callers all await the same underlying request. * fix(auth): guard stale session fetches with generation counter (Codex round 4) Addresses PR #229 round 4 findings: MEDIUM — clear() did not invalidate a pending inflight load, so a pre-logout /auth/session call could resolve after logout and resurrect the logged-out user's session. Subsequent ensureLoaded() calls would also attach to that stale promise. LOW — inflight was only cleared in the promise's finally, so a permanently-hanging fetch wedged loading=true and every subsequent load()/ensureLoaded() returned the same never-settling promise. Fix: introduce a 'generation' counter that bumps on clear(). load() captures the current generation at fetch start and only writes session / clears loading / clears inflight when the generation is still current. clear() now also drops the inflight reference and resets loading=false, so the next ensureLoaded() fires a fresh request and the UI is not left hanging. Late callbacks from pre-logout fetches still resolve in the background but cannot mutate authStore state.
Pad Web UI
SvelteKit 2 + Svelte 5 frontend for Pad, compiled to static files and embedded into the Go binary.
Development
npm install
npm run dev # Dev server at localhost:5173 (proxies API to localhost:7777)
npm run build # Production build to build/
npm run check # Type checking with svelte-check
When developing, run the Go backend separately with make dev from the project root.
Building for Production
Do not build in isolation. Always use make build from the project root — this builds the web frontend, then compiles the Go binary with the build output embedded via //go:embed.
Stack
- Svelte 5 with runes (
$state,$derived,$effect) - SvelteKit 2 with
adapter-static(SPA mode) - Tiptap block editor with markdown round-trip
- svelte-dnd-action for drag-and-drop in board/list views
- SSE for real-time updates
- TypeScript throughout
Structure
src/
routes/ SvelteKit pages
+layout.svelte App shell (sidebar + main)
+page.svelte Landing/redirect
[workspace]/
+page.svelte Dashboard (collections, phases, activity)
+layout.svelte SSE connection per workspace
[collection]/
+page.svelte Collection view (board/list)
[collection]/[item]/
+page.svelte Item detail + editor
conventions/ Purpose-built conventions page
playbooks/ Purpose-built playbooks page
settings/ Workspace settings
lib/
api/client.ts HTTP API client
components/
layout/ Sidebar, navigation
editor/ Tiptap editor, raw markdown editor
fields/ FieldEditor, relation picker
items/ ItemCard, ItemDetail
collections/ BoardView, ListView
common/ StatusBadge, badges, modals
search/ CommandPalette
activity/ ActivityFeed
stores/ Svelte 5 reactive stores
workspace.svelte.ts Workspace state
collections.svelte.ts Collection + item state
ui.svelte.ts Sidebar, mobile state
types/index.ts TypeScript types and constants
app.css Global styles and design tokens