Commit graph

964 commits

Author SHA1 Message Date
83f326c3d1 docs(state): record phase 4 context session 2026-05-03 17:37:10 -04:00
28b5845ee8 docs(04): capture phase context 2026-05-03 17:37:05 -04:00
05a3d44045 docs(phase-3): evolve PROJECT.md after phase completion 2026-05-03 17:09:34 -04:00
7374b94db5 docs(phase-3): complete phase execution 2026-05-03 17:09:13 -04:00
dc8696900b test(03): persist human verification items as UAT 2026-05-03 16:57:42 -04:00
45c8a5d4f5 docs(03-02): complete mobile dashboard page assembly plan summary
- 118-line replacement of app/mobile/dashboard/page.tsx
- DASH-01..04 all satisfied; zero recharts; type-check clean
2026-05-03 16:54:11 -04:00
52562503c0 feat(03-02): replace mobile dashboard page with 3-section layout
- Drop legacy recharts/chart sections, priority breakdown, SLA bars, queue list, recent activity
- Add 2x2 KPI grid (DASH-01), Needs Attention horizontal strip (DASH-02), Worker/backup status row (DASH-03)
- Wire KpiCardMobile, NeedsAttentionStrip, WorkerStatusRow against /api/mobile/dashboard
- Inline error state with Retry button, spinner while loading, refresh button in H1 row
- Zero recharts imports (DASH-04); 118 lines
2026-05-03 16:53:35 -04:00
464c02a7f4 docs(03-01): complete dashboard API + components plan summary 2026-05-03 16:51:30 -04:00
bfe9549d02 feat(03-01): add KpiCardMobile, NeedsAttentionStrip, WorkerStatusRow components
- KpiCardMobile: phone-sized KPI card with label/value/caption; tone='attention' adds destructive left border
- NeedsAttentionStrip: horizontal-scroll strip of compact attention cards; renders null when items=[]
- WorkerStatusRow: 3-cell status row with emerald/amber/destructive status dots, each linking to desktop admin
- All three are pure presentational client components; no fetch, no recharts, no new state libraries
2026-05-03 16:50:35 -04:00
24e20c7ae7 feat(03-01): rewrite /api/mobile/dashboard to return kpis/needsAttention/workers shape
- Exports MobileDashboardResponse, KpiResponse, AttentionResponse, WorkerResponse interfaces
- Single Promise.all with 6 parameterless queries (KPI, failed backups, stalled workflows, analyzer, RMM, backup success)
- Ticket KPIs exclude out-of-scope companies via company_scope filter
- SLA breaches tone='attention' when value > 0
- Worker status rules: down if fail_1h>0 and in_flight=0, warn if fail_1h>0, otherwise ok
- Backup status: ok >= 95%, warn >= 80%, down otherwise
2026-05-03 16:49:49 -04:00
e5e444b36d docs(03): recover plan files and ROADMAP.md after planner worktree-base bug
The planner agent's commit (d597415) was created from a worktree branch
based on master rather than the current feature branch HEAD. The soft-reset
safety check staged a deletion of ROADMAP.md (which exists in the feature
branch but not master), and that deletion was bundled into the agent's
commit. The plan files themselves were never added to the index.

Recovery: stage the on-disk ROADMAP.md (with the planner's Phase 3 updates)
plus the two new PLAN.md files, and commit them cleanly.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 16:31:56 -04:00
170800fb43 docs(phase-03): record planning complete in state 2026-05-03 16:31:00 -04:00
d597415a2f docs(03): create phase plan 2026-05-03 16:28:27 -04:00
7455d1171a docs(phase-02): evolve PROJECT.md after phase completion 2026-05-03 16:17:25 -04:00
53b453d5af docs(phase-02): complete phase execution 2026-05-03 16:16:56 -04:00
8b33dd7955 test(02): persist human verification items as UAT 2026-05-03 16:16:53 -04:00
cc8a584f3f docs(02-02): complete mobile shell wiring plan
- SUMMARY.md: layout rewrite + nav/page.tsx deletion, all 15 phase requirements closed
- STATE.md: decisions, metrics, session recorded
- ROADMAP.md: Phase 02 marked Complete (2/2 plans done)
- REQUIREMENTS.md: SHELL-01, SHELL-05, DRAWER-06 marked complete
2026-05-03 16:12:25 -04:00
2af73955ae feat(02-02): delete app/mobile/nav/page.tsx (DRAWER-06)
- Standalone nav page replaced by MoreDrawer Sheet component
- Visiting /mobile/nav now returns Next.js 404 per DRAWER-06 spec
- No redirect needed — URL was never bookmarked-worthy
2026-05-03 16:09:51 -04:00
7a095fbe53 feat(02-02): rewrite mobile layout to wire HeaderBar + BottomNav + MoreDrawer
- Replace legacy 3-tab layout with new shell components from Plan 01
- Single useState(drawerOpen) shared between avatar and More button triggers
- SHELL-05: main content padded pb-[calc(theme(spacing.16)+env(safe-area-inset-bottom))]
- max-w-lg mx-auto container, no legacy /mobile/nav reference
2026-05-03 16:09:35 -04:00
bb5b62af86 feat(02-01): add /mobile/analyzer placeholder so Analyzer tab does not 404
- Minimal server component with coming soon card
- No data fetching — Phase 6 (ANL-01..06) owns the real implementation
- Sparkles icon consistent with BottomNav tab icon
- Resolves NAV-02: Analyzer tab in bottom nav has a valid route
2026-05-03 16:08:00 -04:00
01943a6dc9 feat(02-01): create BottomNav component with 4 tabs + More button
- Fixed bottom bar with border-t bg-background pb-safe
- 4 tabs: Dashboard, Tickets, Finance, Analyzer linked to /mobile/* routes
- Active state via pathname.startsWith(href), text-primary when active
- More cell triggers onMoreClick prop (parent controls drawer state)
- max-w-lg mx-auto gutter alignment, h-16 (64px) touch targets
- Icons: LayoutDashboard, Ticket, DollarSign, Sparkles, Menu
- Implements SHELL-06, NAV-01..03, DRAWER-01 requirements
2026-05-03 16:08:00 -04:00
05611cd80e feat(02-01): create HeaderBar component with brand, Bell placeholder, avatar trigger
- Sticky header with bg-background/95 backdrop-blur and border-b
- WulfMark + Pulse wordmark linked to /mobile/dashboard
- Bell icon placeholder (aria-label=Notifications, empty onClick per SHELL-03)
- Avatar circle (h-7 w-7) triggers drawer via onAvatarClick prop
- pt-safe applied for notch/dynamic-island clearance (PWA-04)
- Implements SHELL-02..04 requirements
2026-05-03 16:08:00 -04:00
2ef843766d feat(02-01): create MoreDrawer component with Sheet, 3 sections, sign out
- shadcn Sheet side=right with controlled open/onOpenChange props
- Section 1: Mobile sections (Engagement in-shell route)
- Section 2: Full site links with ExternalLink hints (Quotes, Config Items, Backup Status, Ticket Digest, Admin/Sync)
- Section 3: Account — user initials display + sign out via Better Auth
- Implements DRAWER-01..05 requirements
2026-05-03 16:07:47 -04:00
08467494bf docs(02-01): complete plan 02-01 shell components summary
- HeaderBar, BottomNav, MoreDrawer, analyzer placeholder created
- TypeScript and Next.js build both pass (273 routes)
- Requirements SHELL-02..04, SHELL-06, NAV-01..03, DRAWER-01..05 addressed
2026-05-03 16:05:11 -04:00
29ff7fd8fd docs(02): create phase plan 2026-05-03 14:48:41 -04:00
e67c51ec7c docs(02): generate context from PRD 2026-05-03 14:38:17 -04:00
9e29510721 docs(phase-01): evolve PROJECT.md after phase completion 2026-05-03 14:06:04 -04:00
8e6dcf01e1 docs(phase-01): complete phase execution 2026-05-03 14:05:36 -04:00
ca91390f1a test(01): persist human verification items as UAT 2026-05-03 14:03:05 -04:00
a293a4f662 docs(01-02): summarize PWA-04 safe-area utility gap closure
Closes the orphaned PWA-04 requirement flagged by 01-VERIFICATION.md.
Single edit to app/styles/brand.css adds @utility pt-safe and
@utility pb-safe; production build (load-bearing for Tailwind 4
@utility syntax) compiled successfully and emitted both classes
into the production CSS bundle.
2026-05-03 13:57:40 -04:00
dff0264115 feat(01-02): add pt-safe and pb-safe @utility blocks for safe-area insets
- Append @utility pt-safe { padding-top: env(safe-area-inset-top); }
- Append @utility pb-safe { padding-bottom: env(safe-area-inset-bottom); }
- Co-located with existing brand utilities; brand.css already imported by globals.css
- Closes PWA-04 (REQUIREMENTS.md) and ROADMAP Phase 1 SC #3
- Phase 2 sticky header → pt-safe; fixed bottom nav → pb-safe
2026-05-03 13:56:21 -04:00
4c641b9b16 docs(01-pwa-scaffolding): add gap closure plan 01-02 for PWA-04 safe-area utilities 2026-05-03 13:51:06 -04:00
76ced68ac1 docs(01-01): summarize PWA scaffolding plan execution
- Document manifest fields, viewport export shape, and verification results
- Record key decisions (theme_color #0075AD, background_color #FFFFFF,
  metadata.manifest API choice, icon reuse)
- No deviations from plan

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 13:39:55 -04:00
d196d22040 feat(01-01): add manifest reference and viewport export to root layout
- Add metadata.manifest = "/manifest.json" so Next.js emits
  <link rel="manifest"> automatically (PWA-02)
- Add separate viewport export (Next 16 API) with viewportFit: "cover"
  so Phase 2 can paint behind the home indicator (PWA-03)
- Include width: "device-width", initialScale: 1, and themeColor
  light/dark pair for clean mobile rendering
- RootLayout body unchanged

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 13:38:44 -04:00
3e3df24166 feat(01-01): add web app manifest for PWA install
- New public/manifest.json with name "Pulse", short_name "Pulse"
- display "standalone", start_url "/mobile", scope "/"
- theme_color #0075AD (Wulf brand blue), background_color #FFFFFF
- Reuses existing icons (/wulff-logo.png, /branding/wulf-mark.png, /favicon.png)
- No service worker, no next-pwa (per spec §4)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 13:38:08 -04:00
3c62240dc0 docs: create roadmap (8 phases) 2026-05-03 12:48:02 -04:00
7c6df27778 docs: define v1 requirements 2026-05-03 12:43:26 -04:00
d33b8b9131 chore: add project config 2026-05-03 12:41:40 -04:00
fe1401366f docs: initialize mobile shell redesign project 2026-05-03 12:38:56 -04:00
cf3b740d34 docs: map existing codebase 2026-05-03 12:32:06 -04:00
db375fb0e6 feat(admin): client scope — filter analytics to recurring-revenue companies
Adds company-level opt-out scoping so white-label / subcontract clients
(TTG, LEC, PER, VCF, Trivium Packaging, TNT Pizza, etc.) can be excluded
from Wulf's own dashboard KPIs and ticket analytics without affecting
per-company drill-down views.

- migration 082: company_scope table (opt-out; absent row = in scope)
- GET/PATCH /api/admin/company-scope[/companyId] — list + upsert
- /admin/client-scope — searchable company list with Switch per row,
  type filter, and in/out scope filter; excluded rows are dimmed
- dashboard overview KPIs now exclude out-of-scope company tickets
- analyzer /tickets query excludes out-of-scope when no specific
  client is selected (explicit per-company selection still works)
- "Client Scope" tile added to admin Tools & Data section

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 11:40:47 -04:00
2d4a546a8f fix(nav): replace NavigationMenu submenus with DropdownMenu
The Radix NavigationMenu uses a single shared viewport centered under
the full nav list. Opening a right-side submenu (Analyzer, Admin)
positioned the dropdown panel far to the left, so moving the mouse to
reach it crossed other triggers and closed it mid-way.

Switch all items-with-children to DropdownMenu, which positions each
panel directly under its own trigger. Keeps the same visual style
(navigationMenuTriggerStyle, ChevronDown, item layout with icon +
description) and active-state highlighting.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 11:18:52 -04:00
370125d249 feat(admin): group integration toggles by category
Bucket the integration list under category headers (PSA, RMM,
Documentation, Security, …) following a canonical order, with
unrecognized categories appended after. Rows within a bucket sort
alphabetically by name.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 11:03:54 -04:00
344276751e refactor(nav): adopt PageHeader on /configuration-items
The page was rendering its own sticky <header> on top of the global
AppNavigation, which obscured the top nav and duplicated the
ThemeToggle and a redundant "Back to Dashboard" link.

Replace with PageHeader (title, description, breadcrumbs, actions slot
holding Refresh / Bulk-admin / Export) and switch the main container to
the conventional px-6 py-6.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 11:03:49 -04:00
43fbe7b0e1 fix(admin): dedupe device-link conflict candidates by ciId
The reconciler can emit the same configuration_item id multiple times
when more than one match rule fires (e.g. exact_serial AND
hostname_in_company). The page rendered each occurrence as a separate
row keyed on ciId, producing React duplicate-key errors.

Dedupe client-side keeping the strongest confidence (exact_serial > mac
> hostname_in_company > other) and use the deduped count in the badge.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 11:03:44 -04:00
c97e5fc45c feat: status popover + CSV export
Two follow-ons after the ⌘K palette:

StatusIndicator → Popover
- The top-bar status light is no longer a direct link to /status.
  Clicking it opens a popover with grouped issues (failing
  integrations, expired tokens, expiring tokens) so a quick glance
  answers "what's broken" without leaving the current page.  A "View
  full status" link at the bottom routes to /status when needed.
- The trigger keeps the same color rollup so the visual hint is
  visible without opening the popover.

DataTable → CSV export
- Optional `exportable` + `exportFilename` props add an "Export CSV"
  button next to the search bar.  Default behavior exports the current
  page; pass `onExportAll` for server-side full-result downloads.
- Built client-side from column defs (label → header, raw value →
  cell).  BOM-prefixed UTF-8 so Excel decodes correctly.  Quoting +
  escape handled.
- Enabled on /admin/data-browser/{companies,tickets} as initial demos.
  Other data-browser pages opt in by adding two props.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 10:20:27 -04:00
a0894fe946 feat(nav): global ⌘K command palette
Adds a global command launcher reachable from anywhere via ⌘K (Mac) /
Ctrl+K (Win), or "/" when no input is focused.  Three sections:

- **Navigation** — every primary route from the top-bar nav, plus the
  full Admin sub-menu, role-gated against the session.
- **Recent activity** — last 5 audits and last 5 device observations,
  lazy-loaded once on first open from /api/dashboard/overview.
- **Companies** — fuzzy search against the active customer list from
  /api/companies, kicks in once the user types 2+ characters.
  Selecting a company deep-links to /configuration-items?company=<id>.

Top-bar exposes a small "Search · ⌘K" pill on md+ for discoverability,
sized to fit between the Status indicator and the Theme toggle.

Implementation:
- shadcn `command` primitive (uses cmdk under the hood); declined the
  bundled dialog overwrite to keep our existing dialog.tsx.
- CommandPalette mounted once in app/layout.tsx so it lives outside the
  AppNavigation re-renders.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 10:11:08 -04:00
3fa41c25a3 feat(status): 24-hour activity sparklines on worker pulse cards
Each worker card on /status now renders a stacked-bar histogram of the
last 24 hourly buckets — successes from the bottom up in primary blue,
failures from the top down in destructive red, idle hours as a thin
baseline.  Heights normalise to the loudest hour in the series so quiet
workers still show shape.

- /api/status/workers: extended the response with activity24h per
  worker, computed via a generate_series CTE joined to analyzer_jobs /
  rmm_executions / sync_history (zero-fill so the 24-bucket shape is
  consistent regardless of activity).
- ActivitySparkline (components/status/activity-sparkline.tsx) — pure
  flex-end bar strip, no recharts dependency, 32px tall by default.
- WorkerPulse renders the strip below the in-flight / 1h tiles with
  "24h ago" / "now" labels.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 10:03:54 -04:00
e1427b62d7 feat(admin): DB-backed integration toggles + sticky cols + dark contrast
Builds on the env-var INTEGRATIONS_DISABLED shipped with the nav-design
overhaul.  Adds a DB-backed admin UI so operators can flip integrations
without editing .env and restarting the container, plus the remaining
visual cleanup items from the design backlog.

Integration toggles
- Migration 081 — integration_settings table (key PK, disabled flag,
  reason, disabled_by audit, disabled_at).  Seeded with all 13 known
  integrations as enabled.
- GET / PATCH /api/admin/integrations — gated by requirePermission
  (admin, access).  PATCH clears the in-process integration-health
  cache so toggles take effect within seconds.
- /admin/integrations admin page with a Switch per integration, optional
  reason input, audit-info subtitle (disabled by, when, why), live
  status light from /api/dashboard/integration-health.
- integration-health service merges env-var disable list with DB rows;
  degrades gracefully if migration unapplied / DB unreachable.
- Wired into the Admin nav dropdown (eight items now).
- CLAUDE.md describes both env + DB sources.

Sticky first column on tables
- Table primitive accepts stickyFirstColumn?: boolean.  When true, TH
  and TD :first-child stay pinned during horizontal scroll, with
  background inheritance preserving hover and selected row tints.
- DataTable exposes the prop too — on by default for paginated tables.
- /addigy-devices opts in.

Dark-mode contrast
- --border lifted from 10% to 14% in .dark; --input from 15% to 18%;
  --sidebar-border to 14%.
- StatusLight outline ring lifted from /10 to /15 (light) and /20 (dark).
- DetailModal empty-cell em-dash lifted from /40 to /70 so missing
  values are legible on dark surfaces.

DESIGN.md
- Closed sticky-first-column, dark-mode contrast, and palette-audit
  items (palette deprioritized — most uses are semantic).
- Skeleton helpers documented as preferred for new code; existing
  ad-hoc patterns left in place.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 09:55:22 -04:00
ab78e7bd4f refactor(design): adopt TanStack DataTable on /veeam-analysis, fill gaps
Follow-on polish for the nav-design overhaul (#9bfb575).

- /veeam-analysis migrates the bespoke TicketRow + custom pagination to
  the new DataTable using getRowCanExpand + renderSubRow. Drops ~85
  lines of fragment/colspan markup in favor of the standard pattern.

- PageHeader on the last common stragglers — /settings,
  /settings/security, /sentinelone/coverage, /sentinelone/mappings.
  Settings is reachable from the new top-bar UserMenu so it had to
  match the rest of the visual system.

- /dashboard and /status load with the new Skeleton helpers
  (SkeletonRows, SkeletonChart, SkeletonTable) so loading shells now
  approximate the post-load layout instead of a single h-NN bar.

- DESIGN.md: closed the straggler PageHeader item; deprioritized the
  hard-coded palette audit with a note that ~770 references are mostly
  semantic via the documented bg-{hue}-500/15 / text-{hue}-700 recipe.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 09:42:04 -04:00