Phase 45/32 bulk actions framework - #61
Merged
Merged
Conversation
Introduce runtime theming for app icons and Android notification badge support. - Add web/src/lib/appIcon.ts: pure helpers to build SVG app icons, encode as data URLs, and rasterize to PNGs. - Add useDynamicAppIcon hook (web/src/hooks/useDynamicAppIcon.ts) to apply themed favicon, apple-touch-icon, and a generated manifest blob (with revoke handling). Exposes data-base-href so badges can composite over the live themed icon. - Adjust useFaviconBadge to prefer data-base-href when snapshotting/compositing, preventing badge churn over dynamically themed favicons. - Wire the hook into Layout.tsx so it runs before the favicon badge hook. - Add unit tests for useDynamicAppIcon (web/src/hooks/__tests__/useDynamicAppIcon.test.tsx). - Add badge.svg and generated badge-72.png to public icons and document regeneration steps in web/public/icons/README.md. - Update service worker (sw.ts) to use the monochrome badge (/icons/badge-72.png) and clarify badge vs icon behavior in comments. This enables on-the-fly favicon/manifest updates to reflect user-selected themes and ensures Android notifications use the correct monochrome badge to avoid the duplicate-icon bug.
Update PlaybackControls help content to avoid hardcoded white text and improve kbd styling. Added a comment explaining why we must inherit the Tooltip's text color (to avoid invisible text in inverted dark-mode tooltips), removed text-white/* classes, set the grid to use opacity-90, and gave each <kbd> a rounded border, subtle background, padding, and smaller monospace text so shortcut tokens are distinct and readable in both themes.
eslint no-useless-escape was failing on [\s_\-] since the dash is already at end-of-class position; switch to [\s_-]. Pre-existing issue surfaced by adding lint:i18n to the lint chain. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Convert all 31 JS template-literal placeholders (${var}) in
web/src/i18n/en.json to i18next mustache syntax ({{var}}), updating
every callsite to pass interpolation values via t(key, { var,
defaultValue }). Two legitimate ${{amount}} patterns (literal "$"
followed by a mustache placeholder, used for currency formatting) are
preserved.
Add web/scripts/lint-i18n-no-template-literals.mjs as a CI guardrail
that fails if any web/src/i18n/*.json file ever reintroduces this
syntax. The script is chained into "npm run lint" so existing CI
workflows pick it up without further wiring.
Affected user-visible strings include Year-in-Review, Cost-Per-Distance,
Battery Health insights, Notification Rules cadence, Confirm dialogs,
Pin/Unpin tooltip, and Drive efficiency labels.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Adds a centralized broadcast topic registry (TOPICS in web/src/lib/broadcastTopics.ts) and a <FormatterPrefsBridge /> mounted near the React root that anchors a permanent useSettings() subscription so module-level formatter globals (numberFormat locale + decimal precision) stay current on every page — including pages that never call useSettings() themselves. Extends BroadcastMessage with an umbrella settings.changed topic that non-React-Query mutators can fire to invalidate the ['settings'] query on this and peer tabs, and refactors useSettings() to apply globals inside useEffect rather than during render so commits are deterministic under React.StrictMode. Adds 9 broadcast-propagation tests covering: derived useSettings applies/refreshes globals on data change, the bridge anchors globals without a page-level consumer, settings.changed cross-tab broadcasts trigger refetch + global re-application, and invalidateAndBroadcast emits queryInvalidate envelopes on the wire. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…erfiles Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Adds end-to-end measurement of Core Web Vitals (LCP, INP, CLS, FCP, TTFB)
from real browser sessions, the prerequisite for treating the engineering
guideline performance budget (FCP < 1.5s on 4G) as an observable property
rather than a one-off check.
Frontend:
- web/src/lib/webVitalsReporter.ts: idempotent reporter that batches
metrics, ships them via navigator.sendBeacon (with fetch fallback),
flushes on visibilitychange/pagehide, and never propagates errors.
- main.tsx: starts the reporter in PROD; keeps the existing dev-console
reporter for development to avoid HMR noise.
Backend:
- internal/api/web_vitals_handler.go: POST /api/v1/web-vitals ingest
handler with strict JSON decoding, 64 KiB body cap, 100-metric batch
cap, closed allow-list of metric names + ratings, Prometheus
histogram (teslasync_web_vitals_value{name,rating,route}), and route
normalization (numeric/UUID/hex segments collapsed to :id, length cap
50) so label cardinality stays bounded.
- internal/api/router.go: mounted public (no auth) outside the /api/v1
ForwardAuth subrouter and rate-limited to 120 req/min/IP.
UI surface:
- features/system/pages/SystemStatusPage.tsx: new ClientPerformancePanel
documenting that Web Vitals are now collected and linking to a Grafana
dashboard via VITE_GRAFANA_URL when set.
- i18n/en.json: admin.systemStatus.clientPerformance.* keys.
Tests: 9 frontend tests (mocks web-vitals callbacks and exercises
sendBeacon/fetch/error paths) + 8 Go test groups (valid/invalid/empty/
oversize batches, unknown name/rating handling, strict decoding, 14
route-normalization cases, length cap).
Note: the prompt's Files-touched list referenced
features/admin/pages/SystemStatusPage.tsx, but the actual file lives at
features/system/pages/SystemStatusPage.tsx (App.tsx:124,
lazyRoutes.list.ts:135). The git_status allow-list in the gate was
adjusted to match the real path; see PREFLIGHT NOTE in the log.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Adds the missing mobile-aware shared primitives required by the Phase-45/14 mobile responsive audit: - web/src/hooks/useMediaQuery.ts: reactive matchMedia hook with SSR-safe defaults plus useIsMobile() / useIsCoarsePointer() convenience aliases. Lets chart consumers opt into tap-to-tooltip behaviour on touch devices without ad-hoc matchMedia plumbing. - web/src/__tests__/mobile.viewport.test.tsx: smoke test that exercises the shared primitives at a 375px phone viewport (Modal as bottom sheet, BottomTabBar safe-area + touch targets, PageHeader stacking, DataTable mobileColumns) and verifies the useMediaQuery contract (initial state, reactive updates, listener cleanup). Verified: tsc --noEmit, npm run lint, npm test -- --run (1238 passed), audit: viewport-fit=cover + safe-area-inset CSS already present. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add a shared useOptimisticMutation helper that snapshots the cache, writes the optimistic update, restores on error, and invalidates on settle. Supports multi-key updates and an optional cross-tab broadcast. Migrate 7 high-frequency mutations to use it so the UI updates instantly instead of waiting for the server round-trip: - useToggleAutomation (useAutomations.ts) - flip enabled instantly - useDeleteSavedView (useSavedViews.ts) - row removed instantly - useMarkAlertRead (useNotifications.ts) - useToggleAlertRule (useNotifications.ts) - useMarkNotificationsRead (useNotifications.ts) - useMarkNotificationsUnread (useNotifications.ts) - useArchiveNotifications (useNotifications.ts) Notes: - useTogglePin remains on plain useMutation: its mutationFn reads the pinned-list cache to look up the row id for DELETE, which requires pre-mutation cache access. Documented inline; revisit if the backend exposes a delete-by-(type,item_id) endpoint. - Toasts stay on onSuccess/onError, not onMutate, so a rolled-back optimistic write does not mislead the user. - Adds useAlerts.ts re-export shim so consumers can import alert hooks from a dedicated module without churning useNotifications.ts. Tests: - useOptimisticMutation.test.tsx (9 tests): immediate optimistic write, rollback on error, invalidation on settle, multi-key, function-form keys, caller onMutate ordering, double-toggle race, broadcast. - useMarkNotificationRead.test.tsx (6 tests): bulk mark-read + single useMarkAlertRead happy + rollback paths. All gates green: TS clean, lint clean, 1253/1253 frontend tests pass, 13 onMutate occurrences across hooks/. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
… guards Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Replace useState-based filter state with useUrlString/Number/Boolean/Enum helpers in 10 filter-bearing pages so filter selections are bookmarkable, shareable, and survive a page refresh. Also adds a static-source guard test that asserts every listed page imports a useUrl* hook. Pages converted: - charging/ChargingListPage (sort, charger, search, page, dates) - admin/ApiLogsPage (page, method, status, endpoint, service, dates) - admin/DevToolsPage (refactor useTabParam to useUrlEnum) - analytics/StatisticsPage (vehicle, dates) - analytics/PeriodComparePage (vehicle, periodA, periodB) - maps/LocationsPage (vehicle, page, search) - system/CommandHistoryPage (vehicle, status, search, page) - telemetry/SignalExplorerPage (signals, dates, page) - telemetry/SignalDiffPage (vehicle, atA, atB, filter, category) - trips/TripListPage (vehicle, page, dates) Total adopters in features/: 10 -> 20. NEW src/__tests__/url-state-adoption.test.ts asserts the 13 canonical filter pages each call a useUrl* helper. Adds top-level i18n key 'filters.reset' = 'Reset filters'. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…script Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ulse Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…rDisplay Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ency audit Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
… /settings Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…dialog + checklist task Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…pReplayPage Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…update + undo Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…vanced settings Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ken expiry Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ss feature pages Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…t across 4 list pages Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…down + breaker UX Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…I script + dev-time warn
- NEW web/scripts/audit-tooltip-text-color.mjs flags <Tooltip|HelpTooltip>
callsites whose JSX content hardcodes text-white/N or text-gray-{100..400}
(collides with the tooltip's inverted surface — light card in dark mode,
dark card in light mode + light-mode global text-white overrides).
- Wire the audit into the lint chain (npm run audit:tooltip-text).
- web/src/components/ui/Tooltip.tsx: JSDoc the inverted-surface text colour
contract and add a dev-only warnIfHardcodedTextColor backstop that
console.warn's once per offending callsite (zero cost in production
builds via import.meta.env.PROD short-circuit).
- NEW web/src/components/ui/__tests__/Tooltip.contract.test.tsx — 10 tests
covering string content, JSX without overrides, decorative shades
(text-amber-300, text-emerald-300), nested-children walking, dev vs
production behaviour, and the text-gray-500 negative case.
- 0 existing offenders — PlaybackControls helpContent (the post-Phase-40
fix) remains the canonical reference for downstream tooltip authors.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
State Machine debugger no longer shows the contradictory "23 buffered" /
"No transitions in window" pair. The toolbar now reads
"{n} in window . {N} in 24 h" so both scopes are explicit, and the
empty-state timeline + snapshot inspector get an actionable hint with
"Widen window to {label}" and "Jump to last transition" buttons.
Phase-45 / Prompt 35.
Changes:
- new windowTransitions(transitions, minutes, anchor?) helper as the
single source of truth for page, timeline, and toolbar
- new nextWiderPreset(lastTs, anchor, currentMinutes) that picks the
smallest preset (5m..24h) that would surface the last transition
- StateMachineDebuggerPage now feeds pre-windowed transitions to the
timeline and threads lastTransition / widerPreset / handlers into
StateTimeline + SnapshotInspector
- LiveControls renders dual counter + Tooltip explaining the dropdown
vs fetch distinction; bufferCount kept as a one-phase deprecated
fallback (no external consumers)
- StateTimeline empty-state now flex-row with hint + Widen / Jump
buttons; component is purely presentational (callers pre-window)
- SnapshotInspector emptyOutsideWindow branch matches the timeline
hint when there is nothing selectable
- new i18n keys under debugger.{window,timeline,controls,inspector}
Tests: 14 helper + 5 timeline empty + 5 inspector empty + 3 page
integration; full suite 1548/1548 pass; tsc + lint clean.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
… + active-state on chips Fixes the same-tick URL setter race in react-router-dom v6 that caused preset chips on Signal Log Viewer (and 9 other pages) to silently lose one of the two URL writes when applying a time-range preset. Changes: - Add useUrlBatch() to hooks/useUrlState.ts: single setter that atomically commits N URL key changes via one setSearchParams updater call, eliminating the structural race where two same-tick navigate (replace) calls discard each other. - Document the same-tick race in the UrlStateSetter JSDoc. - Extend <DateRangeFilter> with optional onRangeChange prop. When provided, preset chip clicks call it instead of dual onStart/onEnd setters, allowing pages to wire it to a useUrlBatch-backed setter. - Add matchTimeRangePreset() to lib/constants.ts: matches a (from, to) datetime pair to a TIME_RANGE_PRESETS entry within +-60s tolerance. - Add visual active-state to TIME_RANGE_PRESETS chips on SignalLogViewerPage, SignalExplorerPage, and SignalQueryControls (variant=primary + aria-pressed when matchTimeRangePreset matches). - Migrate 10 affected sites: SignalLogViewerPage, SignalExplorerPage and NotificationsPage use useUrlBatch directly; the 8 <DateRangeFilter> consumers (DrivesList, ChargingList, MyActivity, CostAnalysis, Statistics, Energy, Efficiency, TripList) wire onRangeChange. CostAnalysis/Energy/Efficiency also migrated from useState to useUrlString for date filters. - Collapse NotificationsPage.handleFiltersChange (7 setters) into one setFiltersBatch call, fixing silent data loss when applying saved views or clearing all filters. - Add i18n key signalQuery.preset.aria for chip aria-label. Tests: - useUrlBatch: 5 new tests (atomic write, deletion semantics, push option, plus a REGRESSION test documenting the same-tick race that useUrlBatch fixes). - matchTimeRangePreset: 7 new tests (each preset matches, non-match, tolerance, custom tolerance, empty/invalid input). - DateRangeFilter onRangeChange: 3 new tests (precedence, back-compat fallback, onApply still fires). - All 1567 frontend tests pass; tsc clean; lint (incl. all 4 sub-audits) clean. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…eta + add keys endpoint + structured empty-state diagnostic Backend - internal/signal/redis_cache.go: add HLen + Scan to redisSignalClient interface; add RawFieldCount (HLEN raw HSET size, distinguishes "Redis empty" from "fields stored but undecodable") and ScanVehicleKeys (cursor-based SCAN, NEVER KEYS) helpers. - internal/signal/store.go: add LastSeenAt(vehicleID) returning newest L1 timestamp; complement of L2 GetAllValues max-Timestamp scan. - internal/api/devtools_handler.go: wire signalStore + vehicleRepo via new WithSignalStore option; enrich GET /dev-tools/redis-signals with meta block (live_signal_store_mode, redis_key, redis_field_count, l1_signal_count, l1/l2_last_seen_at, vehicle_vin); add sister GET /dev-tools/redis-signals/keys handler that lists vehicleIDs with cached Redis HSETs via SCAN. - internal/api/router.go: pass WithSignalStore(opt.SignalStore) to the dev-tools handler; register the new /redis-signals/keys route. - internal/api/devtools_handler_test.go (NEW): table-driven tests using miniredis covering 503-no-redis, 400-bad-vehicle-id, four meta permutations (mode-local, hybrid+L1-only, hybrid+populated, both empty), and keys endpoint (filters malformed keys, respects limit). Frontend - web/src/api/devtools.ts: add RedisSignalsMeta, RedisSignalKeyEntry, RedisSignalKeysResponse types; getRedisSignalKeys hook function. - web/src/features/admin/components/RedisDiagnosticEmptyState.tsx (NEW): structured 4-branch diagnostic banner — mode=local danger, L1>0+L2=0 mirror-broken warning, no-recent-telemetry info, neutral fallthrough — plus quick-switch chips listing OTHER vehicles that DO have cached signals (from the keys endpoint), and a meta KV list. - web/src/features/admin/pages/RedisSignalViewerPage.tsx: render persistent header chips (Mode/VIN/L1-last) whenever a vehicle is selected so engineers don't have to clear the table to see them; replace the generic empty-state branch with the new diagnostic. - web/src/i18n/en.json: add redis.diagnostic.* (modeLocal, mirrorBroken, noTelemetry, empty, otherVehicles, meta) and redis.headerChip.* blocks. The page stops being a black box: the engineer now gets a specific actionable hint for each of the five known empty-state root causes documented in internal/signal/live_store.go and redis_cache.go. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ts.by_service; trim dead options; add live-writer SERVICE_CONFIG entries Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Stop rescoring each keyword as if it were the label in CommandPalette: score the label once with keywords passed in to preserve label-vs-keyword ranking and avoid keyword ties. Add regression tests to CommandPalette and commandRegistry to lock in the behavior. Revamp service dropdown generation: deriveServiceOptions now accepts an optional knownServices catalog and returns the union of knownServices, by_service keys, and the active service; results are deduplicated and sorted alphabetically (case-insensitive). Add extensive tests to cover union, dedupe, and ordering behaviors. Update ApiLogsPage to use a URL-batch writer (useUrlBatch) so multiple search params (filters + page reset) are written atomically; wire selects/inputs to a setFilter helper and add selectService helper. Introduce KNOWN_SERVICES (from SERVICE_CONFIG) and pass it to deriveServiceOptions; show tracked vs known counts and add several geocoder services to SERVICE_CONFIG. Polish RedisSignalViewerPage value rendering with per-type color classes for better readability. Update i18n string for service count to include tracked and known counts. Tests updated/added accordingly to cover these fixes and prevent regressions.
Drive Detail page (/drives/{id}) was rendering blank Odometer From->To,
blank Range Start->End, blank Range Used, and a flat Power Profile chart
even on drives with valid signal_log telemetry. Two root causes:
1. driveTelemetryFieldMappings was missing the canonical Tesla signals
that the frontend chartData mapper reads: Odometer, IdealBatteryRange,
RatedRange, EstBatteryRange, Soc, HvacFanStatus, HvacLeftTempRequest,
HvacRightTempRequest. The frontend's tp.odometer / tp.idealRange /
tp.ratedRange were always undefined.
2. Tesla Fleet Telemetry does not emit a per-row PackPower signal, so the
chart's dataKey='power' had nothing to render. derivePowerKw() now
computes power = pack_voltage * pack_current / 1000.0 per row (sign
preserved so regen stays negative on the chart). Same formula already
used by enrichLiveDrive() for live drive AvgPowerKw and signalPowerKW()
in telemetry_sessions_signal_helpers.go.
Frontend follow-ups:
- EnergySummaryPanel: replace hardcoded '--' for Range Used with
fmtWithUnit(stats.startRange - stats.endRange, distanceUnit).
- useDriveDetailData: powerMax / powerMin now come from chartData (the
legacy approximation hard-coded powerMax = avgPowerKw and powerMin = 0,
which gave nonsense MaxRegen badges).
Tests:
- TestDriveDetail_Telemetry_DerivesPowerKw: V*I/1000 with sign preservation,
rows missing pack_current have no derived power.
- TestDriveDetail_Telemetry_FieldMappingsCoverPageFields: regression guard
for the 21 fields the frontend reads from each telemetry row.
All 1592 vitest tests + drive detail Go tests pass; tsc clean; audit clean.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
My Activity page rendered 'key activity.myActivity.disabled (en) returned an
object instead of string' as the empty-state body. Same class of bug
in 3 other call sites:
- activity.myActivity.disabled / .unauthorized (MyActivityPage)
- admin.security.timeline (EventTimeline heading)
- widget.chargeHistory (ChargeHistoryWidget title)
- search.noResults / .placeholder / .label (SettingsSearch — also wrong
namespace; CommandPalette owns the search.* tree, settings.search.*
is the right namespace)
Each call resolved its dotted key to an OBJECT (parent of .title /
siblings) in en.json. i18next returns the object verbatim and ignores
the fallback string, so users saw the raw error text in the UI.
Fix:
- Add .description / .title siblings under disabled, unauthorized,
admin.security.timeline, widget.chargeHistory
- Repoint SettingsSearch to settings.search.* (correct namespace)
- New scripts/audit-i18n-shadowed-keys.mjs walks every t('a.b.c') call,
resolves against en.json, fails on any leaf-pointing-to-object.
Wired into npm run lint so this can't regress.
tsc clean; vitest 1592/1592 pass; audit:i18n-shadowed exits 0; audit_code
clean on all 4 touched components.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The Settings struct was missing TimeFormatDefault (Phase-45/22) and ChartPalette (Phase-45/23) fields. When the Appearance settings page PUT them, json.NewDecoder silently dropped the unknown JSON keys, the Upsert never wrote the rows, and the next GET returned the old value - so the radio cards appeared inert when clicked. Wires both fields through the four canonical settings layers: - models.Settings struct (with phase-tagged doc comments) - settingsDefaults() (relative + cb_safe) - applySettingsRow() switch cases - Upsert() textRows - Update() handler validation (rejects values outside the typed enum) No migration needed: the settings key/value table is forward- compatible and will create the rows on first Upsert. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The driving-dynamics cold-signal panels (G-Force, Pedal Usage, Autopilot &
Cruise) and the SignalLogWidget event feed surfaced empty / stale values
because /signals/observations had two coordinated bugs:
1. The repo ordered ASC by ts. The frontend latestNumeric() helper reads
data[0] expecting the most recent observation, so callers asking with
limit:1 got the OLDEST row in the window, and SignalLogWidget showed
the oldest 20 events of the day instead of the most recent 20.
2. The handler defaulted since to now-24h. Cold signals (Lateral/Long
Acceleration, PedalPosition, BrakePedalPos, BrakePedal, CruiseSetSpeed,
CruiseFollowDistance) only emit while driving, so the panels went empty
when the car had been parked > 24h despite the data being on disk.
Fix:
- Repo: ListByName + ListByVehicle now ORDER BY ts DESC and accept zero
time.Time{} bounds to mean 'unbounded' on that side. Extracted the SQL
composition into buildObservationQuery (covered by 5 new unit tests).
The (vehicle_id, signal_name, ts DESC) index serves the bounded LIMIT
case in index-only fashion.
- Handler: omit the lower/upper bound from the predicate when since/until
query params are absent. Reject non-RFC3339 since/until with 400 instead
of silently widening to 'all history' (subtle behavior regression flagged
by rubber-duck review).
Affected callers (all want latest-first): GForcePanel, PedalUsage,
AutopilotSection, SignalLogWidget, PowersharePage. SignalCatalogWidget's
per-signal counts now reflect 'recently active signals' (newest 100 of
all-time) instead of 'oldest 100 of last 24h' — strictly more useful for
the widget's UX.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
atulmgupta
added a commit
that referenced
this pull request
May 7, 2026
…outs) Adds web/src/api/hooks/orphan-allowlist.ts documenting two intentional ORPHAN waivers per phase-43a/0001: - useAlerts.ts: domain-named re-export shim from Phase-45/32 (PR #61); call sites still import from useNotifications. Adoption pending. - useDashboardLayouts.ts: named-layout library hooks with LIVE backend routes (/dashboard/layouts/*) but the UI integration with LayoutSwitcher / DashboardPage is not yet wired (out of scope for this prompt; deferred to a future re-mount prompt). Plus a documented baseline-fix sweep for 8 pre-existing lint failures (4 ESLint no-extra-semi/unused-disable, 4 audit:query-signal AbortSignal threading) so cd web && npm run lint passes end-to-end. Each baseline fix is a single-line edit unrelated to orphan disposition; explicit scope expansion documented in the prompt log per Honesty Covenant 8. See .github/prompts/db-refactor/logs/phase-43a-0001-orphans.log for the full audit evidence, design rationale, and gate output. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
Closes #
Type of Change
Checklist
Screenshots (if applicable)