Commit Graph

3 Commits

Author SHA1 Message Date
JasonFraser d4f2c7d96b Phase 4: Sheet-initiated pull integration (Apps Script) + template import
Rearchitects the Sheet integration per updated CLAUDE.md §15: a
stateless Flask backend has no way to reach into whichever Sheet a PM
has open, so the Sheet pulls its latest results on demand rather than
Flask ever pushing to it. This is also the simpler path for
non-technical users — no Sheet ID to find/paste, no service-account
sharing, just one button in the sidebar.

Apps Script (appscript/, new):
- appsscript.json — Editor Add-on manifest, spreadsheets.currentonly
  scope (touches only whichever Sheet is open, nothing else in Drive)
- Code.gs — onOpen/onInstall/onHomepage, opens the sidebar
- Validation.gs — validateLicence() (6hr cache, POST /validate-email),
  plus markOnboardingStepOnce_() shared helper
- Sync.gs — pullLatestResults()/getWorkbookTabs()/detectColumns(),
  with a real header-alias map for every STANDARD_FIELDS column
  (majorityPrice matches "Rack Rate"/"Standard Price"/etc, not just
  one exact label) so silent write failures don't happen from a PM
  naming a column slightly differently than expected
- Sidebar.html — tab selector + Pull latest results + last-pull status

Backend:
- GET /research/history/latest (email-resolved) replaces the removed
  POST /sheet/push and GET /sheet/tabs
- GET /account/template now reads TEMPLATE_SHEET_URL instead of a
  hardcoded placeholder
- PATCH /account/onboarding now accepts either a JWT session (dashboard)
  or an email in the body (Apps Script has no JWT, only the PM's Google
  email) — this was a real gap: Sync.gs's onboarding-flag calls would
  have 401'd against the JWT-only version
- onboarding_service.DEFAULT_CHECKLIST corrected to the current schema
  (template_imported/sidebar_installed/sync_from_sheet, not the stale
  push_to_sheet/download_template)
- Fixed the same stale field names in CLAUDE.md §32's onboarding
  runbook example payload, so it doesn't teach a future tenant-creation
  script the wrong schema

Frontend:
- PushToSheet.vue removed, replaced by SyncToSheet.vue — a clipboard
  copy button + instructions to use the sidebar, no tab selector (tab
  selection lives in the sidebar, where the workbook is actually open)
- AccountPage.vue's template section rebuilt as three items: open
  template, two-step import instructions + sidebar install link, and
  a deliberately no-op verification input (never validated/fetched/
  stored — the confirmation is purely the PM telling themselves it's
  done, per explicit instruction not to create a dependency on their
  workbook)
- useOnboardingTour.js step 5 updated to match

220 backend tests passing. Frontend production build verified clean.
Apps Script (.gs/.html) cannot be unit-tested in this environment —
no Google Apps Script runtime available locally; verified only by
careful cross-file consistency review (function names/signatures
matching across Code.gs/Validation.gs/Sync.gs/Sidebar.html).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-01 17:29:20 -04:00
JasonFraser c0a77a1a65 Fill in missing Resend templates + Freescout support links
Closes gaps identified after Phase 3: the four email templates named
in CLAUDE.md's file tree (§5) were only partially built (weekly brief
only). Adds:

- welcome.html, trial_reminder.html, price_alert.html, new_product.html
  — matching the brief.html styling pattern
- services/email_service.py — send_welcome_email(),
  send_trial_reminder_email(), send_price_alert_email(),
  send_new_product_email(), all via the same plain-Jinja2 + Resend
  pattern as brief_service.py (side-effect failures logged, never raised)
- Three new internal routes for n8n/manual-onboarding to call:
  POST /internal/welcome-email/<tenant_id>
  POST /internal/trial-reminder/<tenant_id>
  POST /internal/alert-email/<alert_id> (dispatches to price_alert or
  new_product template by alert_type, marks delivered_email on success)
- alert_repository.mark_email_delivered() to back that last route

Also adds the "Contact support" (Freescout) placeholder link CLAUDE.md
calls for in both the sidebar (visible on every page) and AccountPage,
pointing at support.bettersight.io. The actual Freescout container,
DNS/Pangolin routing, and mailbox are real infra this backend can't
provision — the link is a placeholder until that's deployed.

216 backend tests passing, 95% coverage, 100% on the new email_service.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-01 13:32:56 -04:00
JasonFraser 11610b8b4a Phase 3: Vue 3 + Nuxt UI v4 dashboard
Implements CLAUDE.md Build Order Phase 3 — the 7-page dashboard,
matching the approved bettersight-dashboard.html mockup's design
system (dot-grid motif, dark sidebar, card shadows, badges) exactly.

Scope note: nuxt-ui-templates/dashboard-vue (the template CLAUDE.md
says to clone) ships entirely in TypeScript, which directly conflicts
with §18's "JavaScript only, no .ts files" rule. Per user decision,
converted the template to plain JS and rebuilt the dashboard chrome
(sidebar/topbar) as custom components matching the mockup instead of
Nuxt UI's own dashboard components, since the mockup is the approved
design (§25) and Nuxt UI's dashboard components have their own default
look that would fight against it.

- Core plumbing: pocketbase.js client, api_service.js (X-API-Key +
  JWT + 401-refresh interceptor), auth_store/analysis_store/
  notifications_store (Pinia), 5 repositories, explicit vue-router
  (v4, not v5 — matching CLAUDE.md's stack table) with an auth guard
- Shared components: programmatic DotGrid.vue, StatCard, EmptyState,
  CompetitorForm, SeatManager, BattlecardCard, SubscriptionCard,
  CsvUpload+ImportPreviewModal, FeedItem, AlertCard
- All 7 pages: Login (+Google OAuth, forgot password), ResetPassword,
  Account (subscription, upgrade prompt, seats, onboarding), Competitors
  (CSV bulk import), Analyse (full find-urls → confirm → poll → results
  → confidence → push-to-sheet flow with all 4 JobProgress terminal
  states), Activity (stat row, feed, alerts, onboarding tour trigger),
  Battlecards
- Shepherd.js 5-step onboarding tour ported verbatim from §21
- Sentry init (prod-only) + global error handler in main.js

Two backend gaps surfaced and fixed while building this:
- Added GET /account/me (dashboard had no way to learn its own
  tenant_id after login) and fixed GET /account/<tenant_id> to verify
  the session's tenant matches the requested one (previously any valid
  X-API-Key could read any tenant's account data)
- Added GET /alerts and PATCH /alerts/<id>/read (no route existed to
  list or acknowledge alerts, needed by the sidebar badge and
  ActivityPage's alerts panel)

Deliberately deferred: the mockup's Price Position sparkline/corridor
card has no defined data contract anywhere in CLAUDE.md — omitted
rather than backed with fabricated numbers. /sheet/push and
/sheet/tabs still return their Phase-1 structured 501 pending Phase 4.

Verified: full production build succeeds, dev server serves the login
page correctly (screenshotted, zero console errors), and the router's
auth guard correctly redirects an unauthenticated session to
/login?return=... with no console errors. Backend: 202 tests still
passing after the two route additions.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-01 13:16:31 -04:00