Skip to content

chore(deps): bump actions/upload-artifact from 4 to 7#14

Open
dependabot[bot] wants to merge 1 commit into
mainfrom
dependabot/github_actions/actions/upload-artifact-7
Open

chore(deps): bump actions/upload-artifact from 4 to 7#14
dependabot[bot] wants to merge 1 commit into
mainfrom
dependabot/github_actions/actions/upload-artifact-7

Conversation

@dependabot
Copy link
Copy Markdown

@dependabot dependabot Bot commented on behalf of github Mar 22, 2026

Bumps actions/upload-artifact from 4 to 7.

Release notes

Sourced from actions/upload-artifact's releases.

v7.0.0

v7 What's new

Direct Uploads

Adds support for uploading single files directly (unzipped). Callers can set the new archive parameter to false to skip zipping the file during upload. Right now, we only support single files. The action will fail if the glob passed resolves to multiple files. The name parameter is also ignored with this setting. Instead, the name of the artifact will be the name of the uploaded file.

ESM

To support new versions of the @actions/* packages, we've upgraded the package to ESM.

What's Changed

New Contributors

Full Changelog: actions/upload-artifact@v6...v7.0.0

v6.0.0

v6 - What's new

[!IMPORTANT] actions/upload-artifact@v6 now runs on Node.js 24 (runs.using: node24) and requires a minimum Actions Runner version of 2.327.1. If you are using self-hosted runners, ensure they are updated before upgrading.

Node.js 24

This release updates the runtime to Node.js 24. v5 had preliminary support for Node.js 24, however this action was by default still running on Node.js 20. Now this action by default will run on Node.js 24.

What's Changed

Full Changelog: actions/upload-artifact@v5.0.0...v6.0.0

v5.0.0

What's Changed

BREAKING CHANGE: this update supports Node v24.x. This is not a breaking change per-se but we're treating it as such.

... (truncated)

Commits
  • bbbca2d Support direct file uploads (#764)
  • 589182c Upgrade the module to ESM and bump dependencies (#762)
  • 47309c9 Merge pull request #754 from actions/Link-/add-proxy-integration-tests
  • 02a8460 Add proxy integration test
  • b7c566a Merge pull request #745 from actions/upload-artifact-v6-release
  • e516bc8 docs: correct description of Node.js 24 support in README
  • ddc45ed docs: update README to correct action name for Node.js 24 support
  • 615b319 chore: release v6.0.0 for Node.js 24 support
  • 017748b Merge pull request #744 from actions/fix-storage-blob
  • 38d4c79 chore: rebuild dist
  • Additional commits viewable in compare view

Note
Automatic rebases have been disabled on this pull request as it has been open for over 30 days.

@dependabot @github
Copy link
Copy Markdown
Author

dependabot Bot commented on behalf of github Mar 22, 2026

Labels

The following labels could not be found: ci, dependencies. Please create them before Dependabot can add them to a pull request.

Please fix the above issues or remove invalid values from dependabot.yml.

@dependabot dependabot Bot force-pushed the dependabot/github_actions/actions/upload-artifact-7 branch from 3fdc5e7 to 0ebc05d Compare March 22, 2026 02:52
@github-actions github-actions Bot added the ci label Mar 22, 2026
@dependabot dependabot Bot force-pushed the dependabot/github_actions/actions/upload-artifact-7 branch from 0ebc05d to 661d726 Compare March 22, 2026 05:28
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 4 to 7.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](actions/upload-artifact@v4...v7)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-version: '7'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
@dependabot dependabot Bot changed the title chore(deps): Bump actions/upload-artifact from 4 to 7 chore(deps): bump actions/upload-artifact from 4 to 7 Apr 2, 2026
@dependabot dependabot Bot force-pushed the dependabot/github_actions/actions/upload-artifact-7 branch from 661d726 to 09a9717 Compare April 2, 2026 20:50
atulmgupta added a commit that referenced this pull request May 3, 2026
…erfiles

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
atulmgupta added a commit that referenced this pull request May 4, 2026
* Add dynamic themed app icons and notification badge

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.

* Make shortcut keys readable in tooltip

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.

* chore(web): remove unnecessary regex escape in vehicleColors

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>

* phase-45/01-i18n: eradicate template literals + add CI rule

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>

* phase-45/02-boundary: reset ErrorBoundary on route change

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* phase-45/03-formatters: NaN/Infinity-safe duration helpers

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* phase-45/04-modal: migrate hand-rolled overlays to shared <Modal>

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* phase-45/05-smoke: lazy-route bundle smoke test

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* phase-45/06-settings: broadcast propagation for preference mutations

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>

* phase-45/07-sidebar: refine section-header typography

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* phase-45/08-theme: light-mode CSS-var parity re-audit + lint script

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* phase-45/09-help: HelpTooltip adoption on technical screens

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* phase-45/10-docker: backport bug-#14 Go-compile flags to sibling Dockerfiles

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* phase-45/11-pwa: stale-chunk reload resilience + new-version banner

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* phase-45/12-vitals: real-world Web Vitals reporter + Prometheus ingest

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>

* phase-45/13-a11y: comprehensive accessibility audit & landmarks

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* phase-45/14-mobile: responsive audit, safe-area, touch tooltips

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>

* phase-45/15-optimistic: useOptimisticMutation helper + migrate 7 hooks

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>

* phase-45/16-unsaved: NavigationGuardProvider + GuardedLink for in-app guards

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* phase-45/17-urlstate: adopt useUrlState across 13 filter pages

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>

* phase-45/18-skeleton: shaped skeletons across 7 detail pages + audit script

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* phase-45/19-swr: page-header DataFreshnessAuto + background refetch pulse

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* phase-45/20-errors: 404/401/5xx/network branches in QueryError + ErrorDisplay

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* phase-45/21-motion: motion.duration tokens + auditMotionTokens script

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* phase-45/22-time: <TimeStamp> + relative/absolute toggle in Settings

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* phase-45/23-cb: CB-safe chart palette (Okabe-Ito) + user pref + adjacency audit

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* phase-45/24-settings-search: find-as-you-type filter + fuzzy match in /settings

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* phase-45/25-widget-discovery: floating + AddWidgetButton + catalogue dialog + checklist task

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* phase-45/26-replay-sync: bidirectional map ↔ chart cursor sync in TripReplayPage

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* phase-45/27-frecency: command palette frecency ranking + reset action

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* phase-45/28-bulk-read: bulk mark-read in /notifications + optimistic update + undo

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* phase-45/29-silence: ConfirmDialog silenceKey + restore-prompts in Advanced settings

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* phase-45/30-tesla-auth: recovery banner + queued mutation retry on token expiry

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* phase-45/31-empty-state: shared Button + typography + CTA wiring across feature pages

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* phase-45/32-bulk-actions: useBulkSelection + BulkActionToolbar + adopt across 4 list pages

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* phase-45/33-ratelimit: parse Retry-After + RateLimitBanner with countdown + breaker UX

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* phase-45/34-tooltip-audit: catch white-on-white tooltip content via CI 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>

* feat(web): reconcile FSM debugger window vs 24h buffer counts

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>

* phase-45/36-time-range-filter: add useUrlBatch + atomic preset writes + 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>

* phase-45/37-redis-viewer-diag: enrich /dev-tools/redis-signals with meta + 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>

* phase-45/38-api-logs-service-filter: derive Service dropdown from stats.by_service; trim dead options; add live-writer SERVICE_CONFIG entries

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* phase-45/99-gate: phase-45 acceptance contract green

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Fix command scoring; enhance service filters & UI

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.

* fix(api): populate Drive Detail telemetry — odometer, range, power

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>

* fix(web): repair shadowed-parent t() calls + add audit gate

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>

* fix(api): persist time_format_default + chart_palette in settings

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>

* fix(api): /signals/observations latest-first + unbounded since

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>

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
atulmgupta added a commit that referenced this pull request May 28, 2026
Extends web/eslint.config.js with bounded-context element descriptors
covering all 7 frontend hot-spots (per ADR-011 §3):
- src/features/dashboard/widgets/*/**  (capture: domain)
- src/lib/*/**                         (capture: purpose)
- src/api/hooks/*/**                   (capture: domain)
- src/hooks/*/**                       (capture: purpose)
- src/components/ai/*/**               (capture: feature)
- src/components/feedback/*/**         (capture: kind)
- src/components/data-display/*/**     (capture: kind)

Patterns rooted at 'src/...' NOT 'web/src/...' per rubber-duck #13
(ESLint cwd is web/, so web/src/... would classify nothing and mask
R0.5 / R8-R12 progress signals). Capture groups expose the bounded-
context name so R13 can express rules like 'feature widget can only
import from its own domain entities + shared'.

REPORT-MODE TODAY: rules stay at default: 'allow' so the descriptors
are CLASSIFICATORY only - they show the intended shape in lint
output without ever failing the gate. R13 flips default to 'disallow'
with explicit FSD allow lists + boundaries/no-private at error for
components/* categories only (lib/ and hooks/ keep direct subpath
imports per rubber-duck #14 to preserve tree-shaking).

Ordering: most-specific planned-subpkg patterns FIRST so files
already in a subdir win over the catch-all flat-layer patterns
(pages/features/components/hooks/lib/api). Files still at the flat
parent (today's reality) fall through to the existing layer type.

Verified: npm run lint -> exit 0; full 24-audit chain still green.

Refs: docs/architecture/repo-reorganization-plan.md §16.7.1 (R0
deliverables), rubber-duck #13 (pattern rooting), #14 (barrel-only
scope).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
atulmgupta added a commit that referenced this pull request May 28, 2026
Audits all 7 frontend hot-spots and assigns every file to a concrete
target subdir per ADR-011 §1-§7. Pure docs update — no code moved.

Frontend totals from R7 audit:
- web/src/lib                  (104 files) → 13 subdirs
- web/src/hooks                ( 64 files) → 10 subdirs
- web/src/api/hooks            ( 67 files) → 23 subdirs + 2 parent
                                              (mirrors backend R4
                                              database subpkgs 1:1)
- web/src/features/dashboard/widgets
                               (121 files) → 13 subdirs + 4 parent
                                              (registry/types/shell/
                                              barrel kept at parent)
- web/src/components/ai        ( 61 files) → 12 subdirs + 8 parent
                                              (shared AI primitives +
                                              withAiFeature HOC stay)
                                              Mirrors internal/ai/tools/*
                                              topic partition 1:1
- web/src/components/feedback  ( 62 files) → 10 subdirs + 1 parent
- web/src/components/data-display
                               ( 46 files) →  7 subdirs + 1 parent

Key R7 decisions documented in cluster-map.md:
- lib/ + hooks/ use direct subpath imports (NO barrel) for
  tree-shaking (rubber-duck #14)
- components/ai|feedback|data-display use STRICT barrels
  (per ADR-015 amendment §G3 — AI components only callable via
  @/components/ai, never deep imports)
- api/hooks/ subdir names sync 1:1 with internal/database/* from R4
  (R8 CI check: every api/hooks/<x> must be a database subpkg OR
  listed in tools/archmetrics/web_orphans.json — admin, chatbot,
  commands, optim, watch allowed exceptions)
- ai components keep "AI" prefix verbatim per ADR-015 amendment §G3
  (visual marker for guarded components)
- R12 acceptance gate: grep -rL "withAiFeature" components/ai/*/
  MUST return 0 matches
- Duplicate spotted for R12 cleanup: BulkActionsToolbar vs
  BulkActionToolbar (singular/plural)
- Cross-subdir ambiguities flagged for R11 audit:
  · globalShortcuts.tsx → lib/ui (JSX provider) vs
    @/components/keyboard/ (target)
  · signals.ts + signalCatalog.ts → lib/automation vs lib/data
  · cookieConsent.ts → lib/storage vs lib/ui

Source-of-truth coupling:
- Frontend Phase R progress is tracked via eslint-plugin-boundaries
  in web/eslint.config.js (report mode in R0–R12, enforced in R13).
  archmetrics is Go-only and does not need frontend entries.

All gates green: npm run lint (audit:inline-help OK,
audit:touch-target OK with 925 scanned/704 clickable/0 failures).

Refs: ADR-011, ADR-015 amendment, plan v4 §16.5 R7.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

0 participants