Commit 04fc5f8
Chore/repo reorganization (#73)
* chore(blame): ignore R2.0e apperror carve 673da4e1
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* refactor(R2a): carve internal/api/backup (first resource handler subpkg)
First resource carve under Phase R2 — moves the two backup handlers
out of the flat internal/api parent into a self-contained subpkg.
Exercises the full carve pattern (handler types, route mounts,
AppError catalog usage) on a moderately-sized surface (~14 KB across
two files; backup_restore_handler.go was one of the heaviest
writeAppError consumers in the parent).
Why this is the right R2a starter
---------------------------------
- Bounded route prefix: /api/v1/system/backup{,/stats} +
/api/v1/backup/*. No leakage into other resource clusters.
- Heaviest AppError consumer in internal/api by call-site count
(26 writeAppError + 18 WithMessage in backup_restore_handler.go);
proves the post-R2.0e apperror surface end-to-end.
- Modest scope (~14 KB, 13 endpoints) makes a clean reviewable
diff before tackling bigger clusters (vehicle, drives, charging).
New package: internal/api/backup (Layer: handler)
- handler.go Handler (former BackupHandler) — admin-style
data-export endpoints. ExportData streams JSON
for every table in AllowedTables; BackupStats
returns DB size + row counts. AllowedTables is
exported (was unexported allowedBackupTables)
so the regression test in this package and any
future tooling reference one canonical map.
- restore_handler.go RestoreHandler (former BackupRestoreHandler) —
config CRUD (List/Get/Create/Update/Delete),
run management (List/Get), trigger
(TriggerBackup/TriggerQuickBackup), and
download/verify/preview-restore. All error
paths now call apperror.Write directly (no
parent writeAppError wrapper); all success
paths call httpx.WriteJSON directly.
- doc.go Layer: handler + the package-name-collision
rationale (internal/backup is also pkg backup;
imported here as `corebackup`).
Internal cleanups (no behavior change)
- clampConfigBounds extracted from CreateConfig + UpdateConfig
(was duplicated; ~20 LOC each side).
- providerConfigForRun extracted from DownloadBackup +
VerifyBackup + PreviewRestore (was triplicate).
- DRY consolidation visible in the diff but produces byte-
identical responses to the prior implementation.
Parent rewires (internal/api)
- router.go Added `apibackup "internal/api/backup"` import.
Replaced:
NewBackupHandler(db) → apibackup.NewHandler(db)
NewBackupRestoreHandler(db) → apibackup.NewRestoreHandler(db)
Both route mounts unchanged at the method-call site
(var names `backupHandler` / `backupRestoreHandler`
preserved for minimal diff).
- handlers_test.go
Removed TestAllowedBackupTables (relocated to
internal/api/backup/handler_test.go alongside the
canonical apibackup.AllowedTables). Left a Phase
R2a relocation breadcrumb. TestAllowedCommandsWhitelist
stays — commands handler is unrelated and not part
of R2a.
Parent files deleted
- internal/api/backup_handler.go (3196 bytes)
- internal/api/backup_restore_handler.go (11061 bytes)
Tests added
- internal/api/backup/handler_test.go
* TestAllowedTables_RequiredAndForbiddenEntries — pins the
whitelist on both axes (required-present spread; required-
absent dangerous tables like pg_shadow / tokens / api_keys).
External `package backup_test` so it exercises the exported
AllowedTables surface, not the package-internal var.
Gates
- gofmt + goimports clean
- go vet ./internal/api/... + go build ./... clean
- go test ./... PASS (incl. apperror_bridge_test, arch tests)
- go test -race ./internal/api/backup/... PASS
- golangci-lint run ./internal/api/... clean
- go run ./tools/archmetrics -compare tools/archmetrics/baseline.json OK
- baseline.json + baseline.md refreshed
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* chore(blame): ignore R2a backup carve 11ccd511
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* refactor(R2.0f): carve internal/api/apibulk (bulk-endpoint catalog + helpers)
R2.0f lifts the cross-resource bulk-endpoint contract out of the flat
parent internal/api/bulk_helpers.go into a dedicated subpackage. This is
the bulk-API analog of R2.0e (apperror) — shared HANDLER-LAYER
infrastructure that every resource subpackage in the Phase R2 wave can
import directly without coupling to the parent.
Subpkg surface (internal/api/apibulk):
- MaxIDs const (default 500-cap)
- Wire shapes: IDsBody, OpBody, FailedID, OperationResult
- Sentinel decode errors: ErrBodyInvalid, ErrIDsEmpty, ErrIDsTooMany
- Pure helpers: DedupeInt64s, ComputeMissingIDs
- HTTP-coupled helpers: DecodeIDsRequest, DecodeOpBody, WriteBadRequest
Parent bridge (internal/api/bulk_helpers.go):
- TRUE const alias: MaxBulkIDs = apibulk.MaxIDs
- TRUE type aliases: bulkIDsBody, bulkFailedID, bulkOperationResult,
automationBulkBody — identical reflect.Type to canonicals
- Var bridges: errBulkBodyInvalid, errBulkIDsEmpty, errBulkIDsTooMany
- 1-line wrapper funcs: decodeBulkIDsRequest, decodeAutomationBulkBody,
dedupeInt64s, computeMissingIDs, writeBulkBadRequest
Side edit:
- internal/api/automations_bulk_handler.go: deleted local
automationBulkBody type + decodeAutomationBulkBody func (now both
served by the apibulk bridge to avoid duplication). Comment
breadcrumbs left in place.
Tests:
- internal/api/apibulk/helpers_test.go: 18 cases covering decode happy
paths, dedup, sentinel-error mapping, ComputeMissingIDs non-nil
invariant, WriteBadRequest 400 + flat error envelope wire-shape,
sentinel message stability.
- internal/api/bulk_helpers_bridge_test.go: pins const alias,
reflect.Type-equality of all 4 type aliases, errors.Is pointer
equality of the 3 sentinel var bridges, wrapper-delegates-to-
canonical checks for DedupeInt64s + ComputeMissingIDs (incl.
aliased-slice-assignable-to-canonical-slice test).
Why now (in front of R2b geofence):
Nine bulk handlers (alerts, automations, charging, drives, exports,
geofences/bulk, push, saved_views, ...) all currently call the flat
parent helpers. As R2b carves geofence into a subpackage, its bulk
endpoint loses access to the parent (subpkg -> parent imports would
create a cycle). Without apibulk, R2b would have to either duplicate
the 150-LOC helpers into its subpkg (DRY violation, drift risk for the
wire-shape contract the frontend depends on) or block on a much
bigger architectural refactor. R2.0f unblocks every remaining R2
carve cleanly.
ADR-009 exception:
Added bulk_helpers_bridge_test.go row to .github/ARCHITECTURE.md
matching the precedent set by R2.0e/apperror_bridge_test.go. The
bridge test MUST live in package api to name both parent and subpkg
symbols simultaneously.
Verification:
- gofmt -w (clean)
- go vet ./... PASS
- go build ./... PASS
- go test ./... PASS (all ~120 pkgs)
- go test -race ./internal/api/apibulk/... PASS
- go test -race ./internal/api/... PASS
- golangci-lint run ./internal/api/{apibulk,}/... CLEAN
- tools/archmetrics -compare baseline.json OK (no regression)
Wire-shape preserved: parent's flat {"error":"..."} envelope (via
httpx.WriteError) is byte-identical to apibulk.WriteBadRequest; pinned
by TestWriteBadRequest_400FlatShape in the apibulk test suite.
Refs: Phase R2.0e (apperror catalog, 673da4e1) — same pattern.
Refs: Phase R2a (backup subpkg, 11ccd511) — first resource carve, now
unblocked-via-apibulk for R2b.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* chore(blame): ignore R2.0f apibulk carve d5bab8b3
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* refactor(R2b): carve internal/api/geofence (CRUD + bulk handlers)
Moves the /api/v1/geofences resource cluster out of the flat parent
internal/api/ into a dedicated subpackage. Follows the pattern set by
R2a (backup) and uses the shared infrastructure subpackages from
R2.0d/R2.0e/R2.0f (apperror, apibulk, apiparams, httpx) so the
subpackage has zero dependency on the parent.
Carved files (3, blame preserved on 2):
- internal/api/geofence_handler.go -> internal/api/geofence/handler.go (R)
- internal/api/geofence_handler_test.go -> internal/api/geofence/handler_test.go (R)
- internal/api/geofences_bulk_handler.go -> internal/api/geofence/bulk_handler.go (rewritten - new file)
New file:
- internal/api/geofence/doc.go - Layer/Why/Scope/Test-time substitution
Architecture decisions (precedent for R2c/R2d/R2e):
- Options pattern over exported test-only fields: NewHandler(db) for prod,
NewHandler(nil, WithBulkStore(fake)) for tests. No BulkOverride field.
- Audit injection via callback (AuditFunc closure bound in router.go) so
the subpackage can audit bulk_delete events without importing parent.
- AI handler cluster (ai_suggest_new_geofences_*) stays in parent until
R2e; it has its own AISuggestGeofenceValidator that byte-equivalently
mirrors validateGeofence.
Wiring:
- router.go: replaced `NewGeofenceHandler(db)` with
`apigeo.NewHandler(db, apigeo.WithAuditFunc(...))` closure that
delegates to logAuditFromRequest. Method calls unchanged
(List/Create/Get/Update/Delete/BulkUpdate compatible).
- bulk_handlers_phase45_test.go: 4 geofence test cases updated to
`apigeo.NewHandler(nil, apigeo.WithBulkStore(store))`.
fakeGeofenceBulkStore now satisfies apigeo.BulkStore.
Verified:
- gofmt clean
- go vet ./... OK
- go build ./... OK
- go test ./... PASS (all ~120 pkgs)
- go test -race PASS (internal/api/...)
- golangci-lint clean
- archmetrics no architectural regression; baseline refreshed
* chore(blame): ignore R2b geofence carve 927f2b60
* refactor(R2c): carve internal/api/vehicle (core VehicleHandler)
Moves the core /api/v1/vehicles handler — List, Get, Delete, Positions,
CurrentState, Wake, SyncFromTesla — out of the flat parent
internal/api/ into a dedicated subpackage. Sibling resources
(VehicleAccessHandler, VehicleConfigHandler, VehicleInfoHandler,
VehiclePhotoHandler, VehicleSettingsHandler, VehicleStatesHandler) stay
in parent until R2c.1..R2c.6 micro-carves; each has its own constructor
and independent route mount block so they can be carved one at a time.
Carved files (2, blame preserved on both):
- internal/api/vehicle_handler.go -> internal/api/vehicle/handler.go (R)
- internal/api/vehicle_handler_test.go -> internal/api/vehicle/handler_test.go (R)
New file:
- internal/api/vehicle/doc.go - Layer/Why/TelemetryInterface/Scope
Architecture decisions (precedent for sibling vehicle.* carves):
- TelemetrySource interface (1 method: GetLiveSignalStore) decouples the
subpkg from parent *TelemetryHandler. SetTelemetrySource(ts) replaces
SetTelemetryHandler(th *TelemetryHandler) — closing the cycle without
shipping a new domain port.
- Local liveSignalValuesToRaw duplicate. The parent copy in
signal_handler.go stays until its other callers are carved; the
one-line duplication is preferable to closing a cycle on a 5-line
pure helper.
- fakeStateReader duplicated into handler_test.go (4 methods + 1 var
assertion) for the same cycle-avoidance reason. apitest.FakeStateReader
promotion is deferred to a R2.0g pre-carve when more siblings need it.
Drained parent helpers:
- writeAppError wrapper DELETED from helpers.go. R2c was the last
caller, so the wrapper is now dead code; ADR-009 wrapper-deletion
gate met for this specific helper. Other transitional wrappers
(writeJSON, pagination, urlParamInt64, parseDateRange, nullableTime,
writeError, writeErrorCode, writeTeslaTokenExpired) stay until their
respective callers are also drained.
Wiring:
- router.go: added `apiveh "github.com/.../internal/api/vehicle"`
import; replaced NewVehicleHandler(...) with apiveh.NewHandler(...);
renamed SetTelemetryHandler call to SetTelemetrySource.
Verified:
- gofmt clean
- go vet ./... OK
- go build ./... OK
- go test ./... PASS (all ~120 pkgs, no FAIL anywhere)
- go test -race PASS (internal/api/...)
- golangci-lint clean
- archmetrics no architectural regression; baseline refreshed
* chore(blame): ignore R2c vehicle carve 6efc0579
* refactor(R2c.1): carve internal/api/vehicleaccess (drivers + invitations)
First VehicleHandler-sibling micro-carve after R2c core. Moves the
/api/v1/vehicles/{vehicleID}/drivers and
/api/v1/vehicles/{vehicleID}/invitations route clusters out of the
flat parent internal/api/ into a dedicated subpackage.
Carved files (1, blame preserved):
- internal/api/vehicle_access_handler.go -> internal/api/vehicleaccess/handler.go (R)
New file:
- internal/api/vehicleaccess/doc.go - Layer/Why/Scope/Independence
Why a separate subpkg (not under internal/api/vehicle/access):
- Constructor takes only Tesla client + *database.DB; ZERO shared
types/helpers/state with the core VehicleHandler.
- Independent route mount block (/drivers, /invitations) with its own
rate-limit middleware chain.
- Clean carve criterion met: vehicleaccess.Handler does NOT need any
symbol from internal/api/vehicle to function.
truncateBody duplicate:
- 6-line log-trimming helper duplicated locally from the parent
internal/api/tesla_energy_history_handler.go. The parent copy stays
until that handler is also carved.
Wiring:
- router.go: added `apivehaccess "github.com/.../internal/api/vehicleaccess"`
import; replaced NewVehicleAccessHandler(teslaClient, db) with
apivehaccess.NewHandler(teslaClient, db). Method calls at the route
block (ListDrivers/RefreshDrivers/RemoveDriver +
ListInvitations/RefreshInvitations/CreateInvitation/
RevokeInvitation) unchanged.
Verified:
- gofmt clean
- go vet ./... OK
- go build ./... OK
- go test ./... PASS (all packages green, no FAIL anywhere)
- go test -race PASS (internal/api/)
- golangci-lint clean
- archmetrics no architectural regression; baseline refreshed
* chore(blame): ignore R2c.1 vehicleaccess carve 2a0ae4cc
* refactor(R2c.2): carve internal/api/vehicleinfo (Tesla account metadata)
Second VehicleHandler-sibling micro-carve. Moves the per-vehicle Tesla
account metadata cluster — mobile-enabled status, option codes, vehicle
specs, subscription eligibility, upgrade eligibility, warranty details
— out of the flat parent into a dedicated subpackage. All routes stay
under /api/v1/vehicles/{vehicleID}/ unchanged.
Carved files (1, blame preserved):
- internal/api/vehicle_info_handler.go -> internal/api/vehicleinfo/handler.go (R)
New file:
- internal/api/vehicleinfo/doc.go - Layer/Why/Scope/Independence
Independence:
- Constructor takes only Tesla client + *database.DB. ZERO shared
state/types/helpers with the core VehicleHandler or any sibling.
- Single router.go constructor swap; no cross-handler coupling.
- 12 method receivers carved as one unit (6 read + 6 refresh pairs).
Wiring:
- router.go: added `apivehinfo "github.com/.../internal/api/vehicleinfo"`
import; replaced NewVehicleInfoHandler(teslaClient, db) with
apivehinfo.NewHandler(teslaClient, db). Route mount block unchanged
(12 method handler references compatible).
Verified:
- gofmt clean
- go vet ./... OK
- go build ./... OK
- go test ./... PASS (all packages green, no FAIL anywhere)
- go test -race PASS (internal/api/...)
- golangci-lint clean
- archmetrics no architectural regression; baseline refreshed
* chore(blame): ignore R2c.2 vehicleinfo carve ab372b99
* refactor(R2c.3): carve internal/api/vehicleconfig (config history + latest)
Third VehicleHandler-sibling micro-carve. Moves the /api/v1/vehicle-config
List + Latest handlers out of the flat parent into a dedicated subpackage.
Both endpoints are backed by signal.StateReader / signal.LiveStateReader
(ADR-002 / phase-39 change-feed forward-folding); the compound JSON
VehicleConfig payload is flattened to top-level keys (car_type,
trim_badging, exterior_color, wheel_type, ...) so the legacy wire shape
is preserved.
Carved files (2, blame preserved on both):
- internal/api/vehicle_config_handler.go -> internal/api/vehicleconfig/handler.go (R)
- internal/api/vehicle_config_handler_test.go -> internal/api/vehicleconfig/handler_test.go (R)
New file:
- internal/api/vehicleconfig/doc.go - Layer/Why/Scope/Independence
Helpers duplicated locally:
- timelineRowsToFlat (10 lines) duplicated from
internal/api/drive_handler_detail.go. Parent copy stays until that
handler is also carved.
- fakeStateReader + newTestLiveStateReader duplicated into handler_test.go
with the FULL field surface (gotTimelineOpts + gotTimelineFields)
required by the chart-mode CollapseBy + signal-projection assertions.
Independence:
- Constructor takes only (signal.StateReader, signal.LiveStateReader)
— both external. Zero coupling to sibling vehicle.* clusters.
Wiring:
- router.go: added `apivehconfig "github.com/.../internal/api/vehicleconfig"`
import; replaced NewVehicleConfigHandler(stateReader, liveStateReader)
with apivehconfig.NewHandler(...). List + Latest route mounts unchanged.
Verified:
- gofmt clean
- go vet ./... OK
- go build ./... OK
- go test ./... PASS (all packages green, no FAIL anywhere)
- go test -race PASS (internal/api/..., including vehicleconfig)
- golangci-lint clean
- archmetrics no architectural regression; baseline refreshed
* chore(blame): ignore R2c.3 vehicleconfig carve c6d7985e
* refactor(R2c.4): carve internal/api/vehiclestates (FSM transition views)
Phase R2c.4 — fourth VehicleHandler-sibling micro-carve, lifting the
vehicle-states timeline+summary HTTP handler out of the flat
internal/api parent into its own subpackage.
Surface
-------
internal/api/vehiclestates/
doc.go — package contract: layer/why/scope/independence
handler.go — Handler{Timeline, Summary} + vehicleStatesRepository
narrow interface + vehicleStatesClock + window/params
helpers (renamed from VehicleStatesHandler /
NewVehicleStatesHandler).
handler_test.go — fakeVehicleStatesRepo + 31 sub-tests (Decision #8
coverage matrix from phase-43a/0003 preserved).
Wrapper migrations
------------------
- writeError(\u2026) -> httpx.WriteError(\u2026)
- writeJSON(\u2026) -> httpx.WriteJSON(\u2026)
- httpStatusCode(\u2026) -> httpx.HTTPStatusCode(\u2026)
(used by the custom {error,code,max} envelope on days-clamp 400 so the
field surface stays exactly the same.)
Independence
------------
Constructor stays NewHandler(*vehicledb.VehicleStatesRepo) — narrow repo
interface is satisfied by both the production *vehicledb.VehicleStatesRepo
and the in-test fakeVehicleStatesRepo (compile-time conformance check
preserved at the bottom of handler_test.go). vehicleStatesClock pinning
for stable window boundaries is retained verbatim.
Wiring
------
internal/api/router.go
+ import apivehstates "\u2026/internal/api/vehiclestates"
- vehicleStatesHandler := NewVehicleStatesHandler(\u2026)
+ vehicleStatesHandler := apivehstates.NewHandler(\u2026)
Routes (/api/v1/vehicle-states/timeline, /api/v1/vehicle-states/summary)
unchanged.
Gates
-----
- gofmt -w, go vet ./...
- go test -count=1 ./... PASS
- go test -count=1 -race ./internal/api/... PASS
- golangci-lint run \u2026/vehiclestates/... .../api/ clean
- tools/archmetrics -compare OK: no architectural regression
Refs: phase-R2c.4 carve, prior siblings R2c (6efc0579), R2c.1 (2a0ae4cc),
R2c.2 (ab372b99), R2c.3 (c6d7985e).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* chore: ignore R2c.4 vehiclestates carve in git blame
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* refactor(R2c.5): carve internal/api/vehiclesettings (per-vehicle settings)
Phase R2c.5 — fifth VehicleHandler-sibling micro-carve, lifting the
per-vehicle settings handler (List / Put / Delete) out of the flat
internal/api parent into its own subpackage.
Surface
-------
internal/api/vehiclesettings/
doc.go — package contract: layer/why/scope/independence
handler.go — Handler{List, Put, Delete} plus the three injectable
seams (VehicleSettingsOverrideStore,
VehicleSettingsResolverInterface,
VehicleExistenceChecker) and their production
adapter NewVehicleExistenceChecker. Constants
VehicleSettingsCode{InvalidKey, InvalidValue,
NotFound, BadBody} and the 4 KiB
MaxVehicleSettingsBodyBytes guard are preserved
verbatim so the SPA's typed-fetch layer keeps
matching on the same code strings.
handler_test.go — fakeVehicleSettingsStore / Resolver /
ExistenceChecker stubs + full coverage matrix.
Wrapper migrations
------------------
- urlParamInt64(\u2026) -> apiparams.URLParamInt64(\u2026)
- writeError(\u2026) -> httpx.WriteError(\u2026)
- writeErrorCode(\u2026) -> httpx.WriteErrorCode(\u2026)
- writeJSON(\u2026) -> httpx.WriteJSON(\u2026)
Cross-handler dependency
------------------------
vehicle_photo_handler.go (still parent-package; R2c.6 target) uses
both the VehicleExistenceChecker interface and the
VehicleSettingsCodeNotFound constant for its 404 envelope. Updated
those two call sites to fully-qualify against apivehsettings.
fakeVehicleExistenceChecker is duplicated as a small package-private
stub inside vehicle_photo_handler_test.go (Go forbids importing
_test packages, so a transitional copy is the only option) — that
copy vanishes at R2c.6.
Wiring
------
internal/api/router.go
+ import apivehsettings "\u2026/internal/api/vehiclesettings"
- vehicleSettingsHandler := NewVehicleSettingsHandler(\u2026)
+ vehicleSettingsHandler := apivehsettings.NewHandler(\u2026)
- NewVehicleExistenceChecker(\u2026) (x2)
+ apivehsettings.NewVehicleExistenceChecker(\u2026)
Routes (/api/v1/vehicles/{vehicleID}/settings[/{key}]) unchanged.
Gates
-----
- gofmt -w, go vet ./...
- go test -count=1 ./... PASS
- go test -count=1 -race ./internal/api/... PASS
- golangci-lint run \u2026/vehiclesettings/... .../api/ clean
- tools/archmetrics -compare OK: no architectural regression
Refs: phase-R2c.5 carve. Prior siblings: R2c (6efc0579), R2c.1 (2a0ae4cc),
R2c.2 (ab372b99), R2c.3 (c6d7985e), R2c.4 (7670a18e).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* chore: ignore R2c.5 vehiclesettings carve in git blame
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* refactor(R2c.6): carve internal/api/vehiclephoto (photo upload + serve)
Phase R2c.6 — final VehicleHandler-sibling micro-carve, lifting the
four photo endpoints (POST/GET meta, GET file, DELETE) plus the
on-disk encode/atomic-write pipeline out of the flat internal/api
parent into its own subpackage.
With this carve the original VehicleHandler cluster (7 independent
handler types in 12 files spanning ~167 KB) is fully decomposed:
R2c internal/api/vehicle (core)
R2c.1 internal/api/vehicleaccess (drivers + invitations)
R2c.2 internal/api/vehicleinfo (Tesla account metadata)
R2c.3 internal/api/vehicleconfig (config history + latest)
R2c.4 internal/api/vehiclestates (FSM transition views)
R2c.5 internal/api/vehiclesettings (per-vehicle settings KV)
R2c.6 internal/api/vehiclephoto (photo pipeline) \u2190 this commit
Surface
-------
internal/api/vehiclephoto/
doc.go — package contract; documents the one-way
vehiclephoto -> vehiclesettings dep for the shared
VehicleExistenceChecker seam + VEHICLE_NOT_FOUND
envelope code.
handler.go — Handler{GetMeta, GetFile, Upload, Delete} +
per-vehicle upload mutex map; on-disk pipeline
(writeAtomicJPEG, resolveSafePath traversal guard,
cleanupStaged, removeEmptyParent); local
isMaxBytesError duplicate (still consumed by
parent notification/webhook handlers); new
exported IsUploadPath(method,path) helper used
by the router's global body-limit bypass.
Constants MaxUploadBytes, PhotoSize*,
PhotoMaxDimByName, PhotoSizesOrdered,
AllowedPhotoMimeTypes,
VehiclePhotoUploadFormField, and PhotoCode*
preserved verbatim for SPA parity.
handler_test.go — fakeVehiclePhotoStore + minimal local
fakeVehicleExistenceChecker (Go forbids importing
_test packages, so the duplicate that landed
transitionally in R2c.5 stays here permanently);
full coverage matrix retained, real on-disk root
under t.TempDir() so the encode + write pipeline
is exercised end-to-end.
Wrapper migrations
------------------
- urlParamInt64(\u2026) -> apiparams.URLParamInt64(\u2026)
- writeError(\u2026) -> httpx.WriteError(\u2026)
- writeErrorCode(\u2026) -> httpx.WriteErrorCode(\u2026)
- writeJSON(\u2026) -> httpx.WriteJSON(\u2026)
Cross-package dependency
------------------------
vehiclephoto -> vehiclesettings (one-way) for
VehicleExistenceChecker (the seam) and VehicleSettingsCodeNotFound
(the 404 envelope code string). The SPA's typed-fetch layer keys on
the same code string regardless of which handler emits it.
Body-limit bypass move
----------------------
isVehiclePhotoUploadPath(method, path) — the helper consumed by the
global body-limit middleware to widen the 1 MB cap to 12 MB for
POST /api/v1/vehicles/{id}/photo — was a router.go-local function.
Promoted into the subpackage as exported IsUploadPath so the
middleware can reach it without re-implementing the route shape, and
the table-driven test (TestIsUploadPath...) moves along with the
function. router.go now calls apivehphoto.IsUploadPath; the local
copy in router.go is deleted.
Wiring
------
internal/api/router.go
+ import apivehphoto "\u2026/internal/api/vehiclephoto"
- vehiclePhotoHandler := NewVehiclePhotoHandler(\u2026)
+ vehiclePhotoHandler := apivehphoto.NewHandler(\u2026)
- if isVehiclePhotoUploadPath(req.Method, req.URL.Path)
+ if apivehphoto.IsUploadPath(req.Method, req.URL.Path)
- func isVehiclePhotoUploadPath(\u2026) bool { \u2026 } (deleted)
Routes (/api/v1/vehicles/{vehicleID}/photo[/{size}]) unchanged.
Gates
-----
- gofmt -w, go vet ./...
- go test -count=1 ./... PASS
- go test -count=1 -race ./internal/api/... PASS
- golangci-lint run \u2026/vehiclephoto/... .../api/ clean
- tools/archmetrics -compare OK: no architectural regression
Refs: phase-R2c.6 carve, completes the VehicleHandler decomposition
begun at R2c (6efc0579). Sibling SHAs: R2c.1 2a0ae4cc, R2c.2 ab372b99,
R2c.3 c6d7985e, R2c.4 7670a18e, R2c.5 737d3770.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* chore: ignore R2c.6 vehiclephoto carve in git blame
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* refactor(R2d.1): carve internal/api/search subpackage
First non-vehicle resource carve. Moves SearchHandler + PGSearcher into
internal/api/search/ as Handler / PGSearcher (exported because two AI
hydrators consume it directly). Introduces internal/api/search/searchtest
subpackage exporting FakeSearcher so cross-package tests (ai_search,
ai_drive_search) can share the fake — Go forbids importing _test packages.
Files carved:
- internal/api/search_handler.go -> internal/api/search/handler.go
- internal/api/search_handler_test.go -> internal/api/search/handler_test.go
New:
- internal/api/search/doc.go (Layer: handler)
- internal/api/search/searchtest/fake.go (Layer: platform)
- internal/api/search/searchtest/doc.go
Patched parent files:
- ai_search_hydrator.go, ai_drive_search_hydrator.go: import apisearch;
Searcher/SearchHit -> apisearch.X
- ai_search_handler_test.go, ai_drive_search_handler_test.go: import
apisearch + searchtest; newFakeSearcher -> searchtest.NewFakeSearcher;
SearchType*/SearchHit/NewSearchHandlerWithSearcher -> apisearch.X;
.hits[ -> .Hits[, .errs[ -> .Errs[
- router.go: import apisearch; NewSearchHandler -> apisearch.NewHandler;
2x newPGSearcher -> apisearch.NewPGSearcher
Renames inside subpkg:
- SearchHandler -> Handler
- NewSearchHandler -> NewHandler
- NewSearchHandlerWithSearcher -> NewHandlerWithSearcher
- pgSearcher -> PGSearcher (exported for hydrator wiring)
- newPGSearcher -> NewPGSearcher
- SearchType* constants unchanged (already exported)
Wrapper swaps: writeError -> httpx.WriteError, writeJSON -> httpx.WriteJSON.
Gates: go vet clean, go test ./... PASS, -race ./internal/api/... PASS,
golangci-lint clean, archmetrics no regression.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* chore: ignore R2d.1 search carve in git blame
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* refactor(R2d.2): carve internal/api/lifetime subpackage
Moves the /analytics/lifetime handler + ComputeLifetimeStats helper
into internal/api/lifetime/ as Handler / NewHandler. The package-level
ComputeLifetimeStats remains exported because the AI strategy
ai_lifetime_stats_qa consumes it directly to ground its Q&A in the same
deterministic envelope the chart renders.
Files carved:
- internal/api/lifetime_handler.go -> internal/api/lifetime/handler.go
- internal/api/lifetime_handler_test.go -> internal/api/lifetime/handler_test.go
New:
- internal/api/lifetime/doc.go (Layer: handler)
Renames inside subpkg:
- LifetimeHandler -> Handler
- NewLifetimeHandler -> NewHandler
- achievementEventBroadcaster (internal interface) -> EventBroadcaster
(exported port — parent *api.EventHub auto-satisfies it via
BroadcastWithContext, so the subpackage takes ZERO dependency on the
SSE hub concrete type)
- Other exported symbols unchanged: ComputeLifetimeStats,
LifetimeStatsResult, Achievement, PersonalRecord
Patched parent files:
- router.go: import apilifetime; NewLifetimeHandler ->
apilifetime.NewHandler
- ai_lifetime_stats_qa_handler.go: import apilifetime (aliased to avoid
collision with existing internal/ai/tools/lifetime import);
ComputeLifetimeStats -> apilifetime.ComputeLifetimeStats
Wrapper swaps: writeError -> httpx.WriteError, writeJSON -> httpx.WriteJSON.
Local helper: safeFloat duplicated in subpkg (parent api.safeFloat lives
in converters.go and is reused by many other handlers — duplicating
keeps the subpackage free of any dependency on the parent's converters).
Gates: go vet clean, go test ./... PASS, -race ./internal/api/... PASS,
golangci-lint clean, archmetrics no regression.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* chore: ignore R2d.2 lifetime carve in git blame
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* refactor(R2d.3): carve internal/api/notification subpackage
Moves the entire notification cluster (3 handlers, 2 test files) into
internal/api/notification/. Largest carve in the R2 series so far at
~72KB across 5 files.
Files carved:
- notification_handler.go -> notification/handler.go (Handler)
- notification_channel_handler.go-> notification/channel.go (ChannelHandler)
- notification_schedule_handler.go-> notification/schedule.go (ScheduleHandler)
- notification_handler_test.go -> notification/handler_test.go
- notification_channel_handler_test.go -> notification/channel_test.go
New:
- internal/api/notification/doc.go (Layer: handler)
- internal/api/notification/helpers.go (local copies of isMaxBytesError
+ boolPtr — parent originals stay alive because webhook_receiver and
telemetry_sessions_signal_helpers still use them)
Renames inside subpkg:
- NotificationHandler -> Handler
- NewNotificationHandler -> NewHandler
- NotificationChannelHandler -> ChannelHandler
- NewNotificationChannelHandler -> NewChannelHandler
- NotificationScheduleHandler -> ScheduleHandler
- NewNotificationScheduleHandler -> NewScheduleHandler
Outbound API call sink:
The notification adapters (Discord/Slack/Telegram/Webhook/Ntfy/Pushover)
read currentOutboundSink on every call so the most-recent SetOutboundSink
wins. To avoid the subpackage taking a dependency on system_handler.go,
the subpackage exposes a package var SinkProvider func() httputil.APICallSink
which the composition root sets at boot:
apinotif.SinkProvider = currentOutboundSink
The system_handler comment is updated to point at the new wiring.
Patched parent files:
- router.go: import apinotif; 3x NewX swaps; wire SinkProvider
- system_handler.go: doc comment update for SetOutboundSink
Wrapper swaps: writeError -> httpx.WriteError, writeJSON -> httpx.WriteJSON,
urlParamInt64 -> apiparams.URLParamInt64, pagination -> apiparams.Pagination.
Internal adapter helpers move with the handler (no external consumers):
sendDiscord, sendSlack, sendTelegram, sendWebhook, sendNtfy, sendPushover,
postJSON, notifyOutboundClient, normalizeChannelResponse.
Gates: go vet clean, go test ./... PASS, -race ./internal/api/... PASS,
golangci-lint clean, archmetrics no regression.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* chore: ignore R2d.3 notification carve in git blame
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* refactor(R2d.4): carve internal/api/signalinspect subpackage
Moves the per-vehicle signal-inspector handler + the proto-derived
signals catalog into internal/api/signalinspect/. The subpackage name
is signalinspect (NOT signal) to avoid collision with the
internal/signal package both files import.
Files carved:
- signal_handler.go -> signalinspect/handler.go (Handler)
- signal_handler_test.go -> signalinspect/handler_test.go
- signals.go -> signalinspect/catalog.go (AvailableSignals
+ SubscribedSignals)
New:
- internal/api/signalinspect/doc.go (Layer: handler)
Renames inside subpkg:
- SignalHandler -> Handler
- NewSignalHandler -> NewHandler
- liveSignalValuesToRaw -> LiveSignalValuesToRaw (promoted to exported
because alert_handler_rules.go calls it from the parent package)
Already-exported (unchanged): AvailableSignal, AvailableSignals,
SubscribedSignals, WithDB, WithSignalHistory, WithRedisCache,
WithLiveSignalStore, LiveState, Snapshot, Diff, AvailableSignals
method, Stats, History.
Patched parent files:
- router.go: import apisignal; 2x NewSignalHandler -> apisignal.NewHandler
- ai_signal_explorer_nl_filter_handler.go: import apisignal;
AvailableSignals() -> apisignal.AvailableSignals()
- alert_handler_rules.go: import apisignal; liveSignalValuesToRaw ->
apisignal.LiveSignalValuesToRaw
- health.go: import apisignal; SubscribedSignals -> apisignal.SubscribedSignals
Wrapper swaps: writeError -> httpx.WriteError, writeJSON -> httpx.WriteJSON
(13 + 15 call sites).
The signals_catalog_handler.go (separate cluster, different responsibility
— catalog aggregates + observations from signal_log, not per-vehicle live
inspection) is NOT touched by this carve.
Gates: go vet clean, go test ./... PASS, -race ./internal/api/... PASS,
golangci-lint clean, archmetrics no regression.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* chore: ignore R2d.4 signalinspect carve in git blame
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* refactor(R2d.5): carve internal/api/signalscatalog subpackage
Moves the /signals/catalog + /signals/observations global endpoints
into internal/api/signalscatalog/. Distinct from internal/api/signalinspect
(R2d.4) which serves the per-vehicle live-inspector endpoints; catalog +
observations are global (no vehicle scope) and are an ADR-009 exception
restoration introduced by Phase-43a / Prompt 0007.
Files carved:
- signals_catalog_handler.go -> signalscatalog/handler.go (Handler)
- signals_catalog_handler_test.go -> signalscatalog/handler_test.go
New:
- internal/api/signalscatalog/doc.go (Layer: handler)
Renames inside subpkg:
- SignalsCatalogHandler -> Handler
- NewSignalsCatalogHandler -> NewHandler
Wrapper swaps: writeError -> httpx.WriteError (11x),
writeJSON -> httpx.WriteJSON (3x), httpStatusCode -> httpx.HTTPStatusCode.
Patched parent file:
- router.go: import apisigcat; NewSignalsCatalogHandler ->
apisigcat.NewHandler
Gates: go vet clean, go test ./... PASS, -race ./internal/api/... PASS,
golangci-lint clean, archmetrics no regression.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* chore: ignore R2d.5 signalscatalog carve in git blame
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* refactor(R2d.6): carve internal/api/openapi subpackage
Moves the GET /api/v1/system/openapi YAML spec endpoint into
internal/api/openapi/. Tiny standalone carve — single handler with
no cross-package consumers other than the composition root.
Files carved:
- openapi_handler.go -> openapi/handler.go (Handler)
New:
- internal/api/openapi/doc.go (Layer: handler)
Renames inside subpkg:
- OpenAPIHandler -> Handler (constructor function returning http.HandlerFunc)
- SetOpenAPISpec -> SetOpenAPISpec (unchanged; package qualifier disambiguates)
Wrapper swaps: writeError -> httpx.WriteError.
Patched parent files:
- router.go: import apiopenapi; OpenAPIHandler() -> apiopenapi.Handler()
- internal/app/new.go: import apiopenapi; api.SetOpenAPISpec ->
apiopenapi.SetOpenAPISpec at composition-root load site.
Gates: go vet clean, go test ./... PASS, golangci-lint clean,
archmetrics no regression.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* chore: ignore R2d.6 openapi carve in git blame
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* refactor(R2d.7): carve internal/api/synthetic subpackage
Moves the GET /api/v1/admin/observability/synthetic endpoint (ADR-009
exception) into internal/api/synthetic/. Tiny single-handler carve —
exposes a snapshot of every registered synthetic-monitoring probe.
Files carved:
- synthetic_handler.go -> synthetic/handler.go (Handler)
New:
- internal/api/synthetic/doc.go (Layer: handler)
Renames inside subpkg:
- SyntheticHandler -> Handler
- NewSyntheticHandler -> NewHandler
Wrapper swaps: writeError -> httpx.WriteError, writeJSON -> httpx.WriteJSON.
Import-name collision handled: the subpackage is named `synthetic` but
must consume internal/synthetic.Runner — aliased as `synthrun` inside
handler.go to disambiguate from the surrounding package name.
Patched parent files:
- router.go: import apisynthetic; NewSyntheticHandler(opt.SyntheticRunner)
-> apisynthetic.NewHandler(opt.SyntheticRunner)
Gates: go vet clean, go test ./... PASS, golangci-lint clean,
archmetrics no regression.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* chore: ignore R2d.7 synthetic carve in git blame
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* refactor(R2d.8): carve internal/api/visitedlocation subpackage
Moves GET /api/v1/locations into internal/api/visitedlocation/. Tiny
single-handler carve — read-only list of locations the fleet has
visited, with optional vehicle_id query-string scoping.
Files carved:
- visited_location_handler.go -> visitedlocation/handler.go (Handler)
New:
- internal/api/visitedlocation/doc.go (Layer: handler)
Renames inside subpkg:
- VisitedLocationHandler -> Handler
- NewVisitedLocationHandler -> NewHandler
Wrapper swaps: writeError -> httpx.WriteError, writeJSON -> httpx.WriteJSON,
pagination -> apiparams.Pagination.
Patched parent files:
- router.go: import apivisloc; NewVisitedLocationHandler(db) ->
apivisloc.NewHandler(db).
Gates: go vet clean, go test ./... PASS, golangci-lint clean,
archmetrics no regression.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* chore: ignore R2d.8 visitedlocation carve in git blame
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* refactor(R2d.9): carve internal/api/apicalllog subpackage
Moves the GET /api/v1/api-logs and /api-logs/stats read endpoints into
internal/api/apicalllog/. Tiny standalone carve — the writes side
(APICallLogMiddleware + GetAPICallLogger) intentionally stays in the
parent internal/api package because it is part of the chi middleware
chain wired by router.go; only the read handler is carved.
Files carved:
- api_call_log_handler.go -> apicalllog/handler.go (Handler)
New:
- internal/api/apicalllog/doc.go (Layer: handler)
Renames inside subpkg:
- APICallLogHandler -> Handler
- NewAPICallLogHandler -> NewHandler
Wrapper swaps: writeError -> httpx.WriteError, writeJSON -> httpx.WriteJSON,
pagination -> apiparams.Pagination.
Patched parent files:
- router.go: import apicalllog; NewAPICallLogHandler(db) ->
apicalllog.NewHandler(db).
Gates: go vet clean, go test ./... PASS, golangci-lint clean,
gofmt clean, archmetrics no regression.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* chore: ignore R2d.9 apicalllog carve in git blame
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* refactor(R2d.10): carve internal/api/slo subpackage
Moves the GET /api/v1/admin/observability/slo endpoint (ADR-009
exception) into internal/api/slo/. Tiny single-handler carve — returns
one row per SLO declared in slo/catalog.yaml with live SLI ratio,
error budget remaining, and per-tier burn-rate evaluation.
Files carved:
- slo_handler.go -> slo/handler.go (Handler)
New:
- internal/api/slo/doc.go (Layer: handler)
Renames inside subpkg:
- SLOHandler -> Handler
- NewSLOHandler -> NewHandler
Wrapper swaps: writeError -> httpx.WriteError, writeJSON -> httpx.WriteJSON.
Import-name collision handled: the subpackage is named `slo` but must
consume internal/slo.{Catalog,Tracker} — aliased as `slopkg` inside
handler.go to disambiguate from the surrounding package name.
Patched parent files:
- router.go: import apislo; NewSLOHandler(opt.SLOCatalog, opt.SLOTracker)
-> apislo.NewHandler(opt.SLOCatalog, opt.SLOTracker).
Gates: go vet clean, go test ./... PASS, golangci-lint clean,
gofmt clean, archmetrics no regression.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* chore: ignore R2d.10 slo carve in git blame
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* refactor(R2d.11): carve internal/api/geocode subpackage
Moves GET /api/v1/geocode/search and /geocode/reverse into
internal/api/geocode/. Tiny standalone carve — two read-only endpoints
both rate-limited at the router (30/min) over Nominatim (forward) and
the configured commercial reverse provider (Google or Azure Maps).
Files carved:
- geocode_handler.go -> geocode/handler.go (Handler)
New:
- internal/api/geocode/doc.go (Layer: handler)
Renames inside subpkg:
- GeocodeHandler -> Handler
- NewGeocodeHandler -> NewHandler
Wrapper swaps: writeError -> httpx.WriteError, writeJSON -> httpx.WriteJSON.
Patched parent files:
- router.go: import apigeocode; NewGeocodeHandler(...) ->
apigeocode.NewHandler(...).
Gates: go vet clean, go test ./... PASS, golangci-lint clean,
gofmt clean, archmetrics no regression.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* chore: ignore R2d.11 geocode carve in git blame
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* refactor(R2d.12): carve internal/api/dataquality subpackage
Moves the GET /admin/observability/data-quality + /lineage endpoints
(ADR-009 exception) into internal/api/dataquality/. Tiny standalone
carve — per-field freshness/max-gap/duplicate-ratio scoring over
signal_log plus a static pipeline DAG.
Files carved:
- dataquality_handler.go -> dataquality/handler.go (Handler)
New:
- internal/api/dataquality/doc.go (Layer: handler)
Renames inside subpkg:
- DataQualityHandler -> Handler
- NewDataQualityHandler -> NewHandler
Wrapper swaps: writeError -> httpx.WriteError, writeJSON -> httpx.WriteJSON.
Import-name collision handled: subpkg is named `dataquality` but must
consume internal/dataquality.{Scorer,ErrNotConfigured,BuildLineage} —
aliased as `dqpkg` inside handler.go.
Patched parent files:
- router.go: import apidq; NewDataQualityHandler(opt.DataQualityScorer)
-> apidq.NewHandler(opt.DataQualityScorer).
Gates: go vet clean, go test ./... PASS, golangci-lint clean,
gofmt clean, archmetrics no regression.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* chore: ignore R2d.12 dataquality carve in git blame
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* refactor(R2d.13): carve internal/api/softwareupdate subpackage
Moves GET /api/v1/software-updates into internal/api/softwareupdate/.
Tiny single-handler carve — durable history of Tesla over-the-air
firmware updates with optional vehicle_id scoping plus standard
start/end date range and limit pagination.
Files carved:
- software_update_handler.go -> softwareupdate/handler.go (Handler)
New:
- internal/api/softwareupdate/doc.go (Layer: handler)
Renames inside subpkg:
- SoftwareUpdateHandler -> Handler
- NewSoftwareUpdateHandler -> NewHandler
Wrapper swaps: writeError -> httpx.WriteError, writeJSON -> httpx.WriteJSON,
pagination -> apiparams.Pagination, parseDateRange -> apiparams.ParseDateRange.
Patched parent files:
- router.go: import apisoftupd; NewSoftwareUpdateHandler(db) ->
apisoftupd.NewHandler(db).
- ai_software_update_changelog_summarizer_handler.go: docstring
`SoftwareUpdateHandler` -> `Handler` (comment only).
Gates: go vet clean, go test ./... PASS, golangci-lint clean,
gofmt clean, archmetrics no regression.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* chore: ignore R2d.13 softwareupdate carve in git blame
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* refactor(R2d.14): carve internal/api/exportcolumns subpackage
Moves GET /api/v1/exports/columns into internal/api/exportcolumns/.
Tiny standalone carve — returns publishable column metadata for each
export job type so the frontend column picker can render checkboxes
without hard-coding the catalog (Phase-46 / Prompt 62).
Files carved:
- exports_columns_handler.go -> exportcolumns/handler.go (Handler)
- exports_columns_handler_test.go -> exportcolumns/handler_test.go
New:
- internal/api/exportcolumns/doc.go (Layer: handler)
Renames inside subpkg:
- ExportColumnsHandler -> Handler
- NewExportColumnsHandler -> NewHandler
- TestExportColumnsHandler_ListColumns -> TestHandler_ListColumns
Wrapper swaps: writeError -> httpx.WriteError, writeJSON -> httpx.WriteJSON.
Patched parent files:
- router.go: import apiexpcol; NewExportColumnsHandler() ->
apiexpcol.NewHandler().
Gates: go vet clean, go test ./... PASS
(internal/api/exportcolumns 0.191s), golangci-lint clean, gofmt clean,
archmetrics no regression.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* chore: ignore R2d.14 exportcolumns carve in git blame
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* refactor(R2d.15): carve internal/api/weeklydigest subpackage
Moves GET /api/v1/vehicles/{vehicleID}/weekly-digest into
internal/api/weeklydigest/. Tiny single-handler carve — returns
aggregated stats (drives, distance, energy, cost, efficiency) for the
current vs previous week. Reads Phase-42 SI canonical drives table
(distance_m, energy_used_wh) and converts to km/kWh on the wire to
preserve the legacy frontend contract.
Files carved:
- weekly_digest_handler.go -> weeklydigest/handler.go (Handler)
New:
- internal/api/weeklydigest/doc.go (Layer: handler)
Renames inside subpkg:
- WeeklyDigestHandler -> Handler
- NewWeeklyDigestHandler -> NewHandler
Wrapper swaps: writeError -> httpx.WriteError, writeJSON -> httpx.WriteJSON,
urlParamInt64 -> apiparams.URLParamInt64.
Patched parent files:
- router.go: import apiweekly; NewWeeklyDigestHandler(db) ->
apiweekly.NewHandler(db).
Gates: go vet clean, go test ./... PASS, golangci-lint clean,
gofmt clean, archmetrics no regression.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* chore: ignore R2d.15 weeklydigest carve in git blame
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* refactor(R2d.16): carve internal/api/tco subpackage
Splits the TCO (Total Cost of Ownership) handler off the flat
internal/api/ parent package into its own subpackage:
internal/api/tco/
doc.go (new — Layer: handler)
handler.go (was tco_handler.go)
summary.go (was tco_summary.go)
summary_test.go (was tco_summary_test.go)
* TCOHandler renamed to Handler; NewTCOHandler renamed to NewHandler
per the established carve naming convention.
* httpx wrapper swaps applied (writeError -> httpx.WriteError,
writeJSON -> httpx.WriteJSON).
* Shared helpers ComputeTCOSummary, TCOSummary, TCOMonthlyEntry stay
exported so the AI consumer (ai_tco_narration_handler.go, which
remains in the parent package) can still reach them via the new
apitco import alias.
* godoc cross-references updated from *TCOHandler.GetTCO to *Handler.GetTCO
inside the new subpackage.
* Router (internal/api/router.go) and ai_tco_narration_handler.go
updated to import the new subpackage with alias 'apitco' and call
apitco.NewHandler / apitco.ComputeTCOSummary.
No behavior change. archmetrics baseline refreshed: no architectural
regression. Pure code move + package split.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* chore: ignore R2d.16 tco carve in git-blame
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* refactor(R2d.19): carve internal/api/onboarding subpackage
Splits the onboarding handler off the flat internal/api/ parent
package into its own subpackage:
internal/api/onboarding/
doc.go (new - Layer: handler)
handler.go (was onboarding_handler.go)
handler_test.go (was onboarding_handler_test.go)
* OnboardingHandler renamed to Handler; NewOnboardingHandler renamed
to NewHandler per the established carve naming convention.
* httpx + apiparams wrapper swaps applied.
* Router updated to import alias 'apionboard' and call apionboard.NewHandler.
No behavior change. Pure code move + package split.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* refactor(R2d.18): carve internal/api/teslauserprofile subpackage
Splits the Tesla user profile handler off the flat internal/api/ parent
package into its own subpackage:
internal/api/teslauserprofile/
doc.go (new - Layer: handler)
handler.go (was tesla_user_profile_handler.go)
* TeslaUserProfileHandler renamed to Handler; NewTeslaUserProfileHandler
renamed to NewHandler per the established carve naming convention.
* httpx + apiparams wrapper swaps applied.
* Router updated to import alias 'apitup' and call apitup.NewHandler.
No behavior change. Pure code move + package split.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* chore(R2d.18-19): refresh archmetrics baseline + ignore fleet carves in git-blame
First successful parallel-fleet batch: R2d.18 (teslauserprofile) and R2d.19 (onboarding)
carved in separate git worktrees by background agents, then cherry-picked into
chore/repo-reorganization. R2d.17 (settingsexport) deferred — needs joint carve
with settings_import due to shared fakeSettingsRepo/fakeAlertRepo/fakeGeofenceRepo/
fakeQuietHoursRepo + newTestSettingsSerializer.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* refactor(R2d.20): carve internal/api/trip subpackage
Splits the trip handler off the flat internal/api/ parent package
into its own subpackage:
internal/api/trip/
doc.go (new - Layer: handler)
handler.go (was trip_handler.go)
* TripHandler renamed to Handler; NewTripHandler renamed to NewHandler
per the established carve naming convention.
* httpx + apiparams wrapper swaps applied.
* Router updated to import alias 'apitrip' and call apitrip.NewHandler.
* Distinct from internal/handler/v1.TripHandler (separate package, untouched).
No behavior change. Pure code move + package split.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* refactor(R2d.21): carve internal/api/webhookreceiver subpackage
Splits the webhook receiver handler off the flat internal/api/ parent
package into its own subpackage:
internal/api/webhookreceiver/
doc.go (new - Layer: handler)
handler.go (was webhook_receiver_handler.go)
handler_test.go (was webhook_receiver_handler_test.go)
* WebhookReceiverHandler renamed to Handler; NewWebhookReceiverHandler
renamed to NewHandler per the established carve naming convention.
* httpx + apiparams wrapper swaps applied.
* Router updated to import alias 'apiwhrx' and call apiwhrx.NewHandler.
* Distinct from the dead webhook_handler.go (slated for deletion in
a later Phase R cleanup).
No behavior change. Pure code move + package split.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* chore(R2d.20-21): refresh archmetrics baseline + ignore fleet batch 2 in git-blame
Second successful parallel-fleet batch: R2d.20 (trip) and R2d.21 (webhookreceiver)
carved in separate git worktrees by background agents, then cherry-picked into
chore/repo-reorganization. Webhookreceiver agent also patched a router_middleware.go
parent consumer that the pre-fleet survey had missed.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* refactor(R2d.22): carve internal/api/teslauserorder subpackage
Splits the Tesla user order handler off the flat internal/api/ parent
package into its own subpackage:
internal/api/teslauserorder/
doc.go (new - Layer: handler)
handler.go (was tesla_user_order_handler.go)
* TeslaUserOrderHandler renamed to Handler; NewTeslaUserOrderHandler
renamed to NewHandler per the established carve naming convention.
* httpx + apiparams wrapper swaps applied.
* Router updated to import alias 'apituo' and call apituo.NewHandler.
No behavior change. Pure code move + package split.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* refactor(R2d.23): carve internal/api/teslauserconfig subpackage
Splits the Tesla user config handler off the flat internal/api/ parent
package into its own subpackage:
internal/api/teslauserconfig/
doc.go (new - Layer: handler)
handler.go (was tesla_user_config_handler.go)
* TeslaUserConfigHandler renamed to Handler; NewTeslaUserConfigHandler
renamed to NewHandler per the established carve naming convention.
* httpx + apiparams wrapper swaps applied.
* Router updated to import alias 'apituc' and call apituc.NewHandler.
No behavior change. Pure code move + package split.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* refactor(R2d.24): carve internal/api/gasprice subpackage
Splits the gas price handler off the flat internal/api/ parent package
into its own subpackage:
internal/api/gasprice/
doc.go (new - Layer: handler)
handler.go (was gas_price_handler.go)
* GasPriceHandler renamed to Handler; NewGasPriceHandler renamed to
NewHandler per the established carve naming convention.
* httpx + apiparams wrapper swaps applied.
* Router updated to import alias 'apigas' and call apigas.NewHandler.
No behavior change. Pure code move + package split.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* refactor(R2d.25): carve internal/api/apikey subpackage
Splits the API key handler off the flat internal/api/ parent package
into its own subpackage:
internal/api/apikey/
doc.go (new - Layer: handler)
handler.go (was apikey_handler.go)
* APIKeyHandler renamed to Handler; NewAPIKeyHandler renamed to
NewHandler per the established carve naming convention.
* httpx + apiparams wrapper swaps applied.
* Router updated to import alias 'apikeyh' and call apikeyh.NewHandler.
No behavior change. Pure code move + package split.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* refactor(R2d.26): carve internal/api/feedback subpackage
Splits the feedback handler off the flat internal/api/ parent package
into its own subpackage:
internal/api/feedback/
doc.go (new - Layer: handler)
handler.go (was feedback_handler.go)
handler_test.go (was feedback_handler_test.go)
* FeedbackHandler renamed to Handler; NewFeedbackHandler renamed to
NewHandler per the established carve naming convention.
* httpx + apiparams wrapper swaps applied.
* Router updated to import alias 'apifb' and call apifb.NewHandler.
No behavior change. Pure code move + package split.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* chore(R2d.22-26): refresh archmetrics baseline + ignore fleet batch 3 in git-blame
Third parallel-fleet batch: 5 carves in parallel via background agents
in separate git worktrees. Two minor merge conflicts on router.go (overlapping
import insertion + ctor call regions) resolved trivially by keeping both.
Carves:
R2d.22 teslauserorder
R2d.23 teslauserconfig
R2d.24 gasprice
R2d.25 apikey (also patched apikey_middleware.go)
R2d.26 feedback (also patched helpers.go - kept firstNonEmpty for other consumers)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* refactor(R2d.31): carve internal/api/drivediagnostic subpackage
Splits the drive diagnostic handler off the flat internal/api/ parent
package into its own subpackage:
internal/api/drivediagnostic/
doc.go (new - Layer: handler)
handler.go (was drive_diagnostic_handler.go)
handler_test.go (was drive_diagnostic_handler_test.go)
* DriveDiagnosticHandler renamed to Handler; NewDriveDiagnosticHandler
renamed to NewHandler per the established carve naming convention.
* httpx + apiparams wrapper swaps applied.
* Router updated to import alias 'apidrived' and call apidrived.NewHandler.
No behavior change. Pure code move + package split.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* refactor(R2d.30): carve internal/api/user subpackage
Splits the user handler off the flat internal/api/ parent package
into its own subpackage:
internal/api/user/
doc.go (new - Layer: handler)
handler.go (was user_handler.go)
* UserHandler renamed to Handler; NewUserHandler renamed to NewHandler
per the established carve naming convention.
* httpx wrapper swaps applied.
* Router had no flat NewUserHandler consumer at HEAD; internal/handler/v1.UserHandler
remains untouched.
* Distinct from internal/handler/v1.UserHandler (separate package, untouched).
No behavior change. Pure code move + package split.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* refactor(R2d.28): carve internal/api/teslaenergylivestatus subpackage
Splits the Tesla energy live status handler off the flat internal/api/
parent package into its own subpackage:
internal/api/teslaenergylivestatus/
doc.go (new - Layer: handler)
handler.go (was tesla_energy_live_status_handler.go)
* TeslaEnergyLiveStatusHandler renamed to Handler;
NewTeslaEnergyLiveStatusHandler renamed to NewHandler per the
established carve naming convention.
* httpx + apiparams wrapper swaps applied.
* Router updated to import alias 'apitels' and call apitels.NewHandler.
No behavior change. Pure code move + package split.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* refactor(R2d.27): carve internal/api/auth subpackage
Splits the auth handler off the flat internal/api/ parent package
into its own subpackage:
internal/api/auth/
doc.go (new - Layer: handler)
handler.go (was auth_handler.go)
* AuthHandler renamed to Handler; NewAuthHandler renamed to NewHandler
per the established carve naming convention.
* httpx + apiparams wrapper swaps applied.
* Router updated to import alias 'apiauth' and call apiauth.NewHandler.
No behavior change. Pure code move + package split.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* refactor(R2d.29): carve internal/api/periodstats subpackage
Splits the period stats handler off the flat internal/api/ parent
package into its own subpackage:
internal/api/periodstats/
doc.go (new - Layer: handler)
handler.go (was period_stats_handler.go)
* PeriodStatsHandler renamed to Handler; NewPeriodStatsHandler renamed
to NewHandler per the established carve naming convention.
* httpx + apiparams wrapper swaps applied.
* Router updated to import alias 'apiperiod' and call apiperiod.NewHandler.
No behavior change. Pure code move + package split.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* chore(R2d.27-31): refresh archmetrics baseline + ignore fleet batch 4 in git-blame
Fleet batch 4 carves (parallel worktrees, cherry-picked into main):
- R2d.27 auth (0e758201)
- R2d.28 teslaenergylivestatus (9f630e42)
- R2d.29 periodstats (04373b2e, exports ComputePeriodStats)
- R2d.30 user (0aade829, orphan handler — no router change)
- R2d.31 drivediagnostic (c745a5a7)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* refactor(R2d.32): carve internal/api/ingestxray subpackage
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* refactor(R2d.33): carve internal/api/webvitals subpackage
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* refactor(R2d.34): carve internal/api/weberrors subpackage
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* refactor(R2d.35): carve internal/api/pinned subpackage
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* refactor(R2d.36): carve internal/api/dlq subpackage
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* refactor(R2d.37): carve internal/api/impersonate subpackage
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* chore(R2d.32-37): refresh archmetrics baseline + ignore fleet batch 5 in git-blame
Fleet batch 5 (6 parallel carves, scaled up from 5):
- R2d.32 ingestxray (4a1867c0)
- R2d.33 webvitals (ce883968, exports NormalizeRoute)
- R2d.34 weberrors (fb365cea, integration: dedup local normalizeWebVitalsRoute → apivitals.NormalizeRoute)
- R2d.35 pinned (553b88f8, patched saved_views_handler_test.go)
- R2d.36 dlq (cd8536fe, patched flags_handler.go)
- R2d.37 impersonate (28818b2d, patched ai_pii_redaction_shared_exports_handler.go)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* refactor(R2d.38): carve internal/api/tripsdetail subpackage
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* refactor(R2d.39): carve internal/api/authsession subpackage
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* refactor(R2d.40): carve internal/api/quiethours subpackage
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* refactor(R2d.41): carve internal/api/apiflagsh subpackage
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* refactor(R2d.42): carve internal/api/sysauthmode subpackage
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* refactor(R2d.43): carve internal/api/ratelimit subpackage
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* refactor(R2d.44): carve internal/api/savedviews subpackage
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* refactor(R2d.47): carve internal/api/diagnostic subpackage
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* refactor(R2d.45): carve internal/api/mileage subpackage
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* refactor(R2d.46): carve internal/api/anomaly subpackage
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.co…1 parent e155065 commit 04fc5f8
3,144 files changed
Lines changed: 236844 additions & 213664 deletions
File tree
- .github
- scripts
- cmd
- audit-signal-types
- automation-worker
- backup-verify
- chaos-runner
- export-worker
- fleet-config-validator
- metric-coverage-audit
- notification-worker
- ocpp-server
- protogen-tesla
- pub-test-signal
- resubscribe
- slo-coverage-audit
- slogen
- teslasync
- trace-coverage-audit
- unit-drift-validator
- db/seeds
- docs
- architecture
- adr
- migration
- archive
- internal
- adapter
- geocoding
- mqtt
- postgres
- queries
- redis
- storage
- tesla
- ai
- cost
- dispatch
- eval
- features
- guard
- health
- limit
- provider
- anthropic
- azure
- mock
- ollama
- openai
- rag
- redact
- strategies
- alert-tuning-suggestions
- anomaly-explanations
- auto-name-unnamed-locations
- auto-trip-naming
- battery-health-forecast-narrative
- cabin-temperature-impact-narrative
- charging-curve-fingerprint-clustering
- charging-diagnosis
- chatbot-llm
- cost-forecast-narration
- cross-rule-conflict-detection
- data-repair-suggestions
- digest-narration
- drive-coaching
- feedback-queue-triage
- geofence-aware-automation-suggestions
- inbox-auto-categorization
- incident-timeline-summarizer
- learned-per-vehicle-anomaly-baselines
- lifetime-stats-qa
- log-trace-summarization
- ml-charging-curve-clustering
- mqtt-sse-inspector-explanations
- nl-alert-builder
- nl-automation-builder
- nl-dashboard-composer
- nl-drive-search-replay
- nl-grafana-panel
- nl-search
- nl-sql-playground
- period-compare-narration
- predictive-maintenance
- preheat-precool-recommender
- quiet-hours-suggestion
- rag-help
- range-prediction-model
- route-efficiency-suggestions
- safety-setting-explainer
- signal-explorer-nl-filter
- smart-charge-schedule-suggestion
- software-update-changelog-summarizer
- speed-profile-insights
- state-machine-debugger-narrator
- suggest-new-geofences
- tco-narration
- tire-pressure-trend-reasoning
- trip-planner-llm-agent
- vampire-drain-explanation
- vehicle-paint-preview
- voice-mode
- watch-face-nl-response
- yir-narration
- strategy
- stream
- tools
- alert
- anomaly
- automation
- charge
- coaching
- curve
- diagnosis
- diagnostic
- digest
- export
- feedback
- forecast
- lifetime
- location
- maintenance
- nlq
- nl
- paint
- predict
- route
- safety
- schedule
- speed
- summary
- toolstest
- tripplan
- trip
- voice
- yir
- alertmsg
- apilog
- api
- adminfeedback
- adminlogstream
- adminmaintenance
- aialerttune
- aialert
- aianomaly
- aiautomation
- aiautoname
- aiautotripname
- aibatthealth
- aichargcurve
- aichargdiag
- aichatbot
- aiclimate
- aicostfcst
- aicrossrule
- aidatarep
- aidigest
- aidrivecoach
- aidrivesearch
- aifeedtri
- aifsmnar
- aigeofautom
- aiinboxcat
- aiincident
- ailifetime
- ailogtrace
- aimlanom
- aimlchargcv
- aimlrange
- aimqttsse
- ainldash
- ainlgrafana
- ainlsql
- aiperiodcmp
- aipiiredact
- aipostcard
- aipredmaint
- aiquiethrs
- airaghelp
- airouteeff
- aisafetyexp
- aisearch
- aisettingsvalidate
- aisignalnl
- aismartcharge
- aispeedprof
- aisuggeo
- aiswupd
- aitconar
- aitempimpact
- aitirepress
- aitripplanllm
- aiusage
- aivampire
- aivehpaint
- aivoice
- aiwatchnl
- aiyir
- alertmsg
- alerts
- analytics
- anomaly
- apibulk
- apicalllog
- apiflagsh
- apikey
- apiparams
- apitest
- apperror
- audit
- authsession
- auth
- automation
- backup
- batterycells
- batterydegradation
- battery
- chargeheatmap
- chargeopt
- chargeplanner
- chargetelem
- charging
- chartannotation
- chatbot
- climate
- command
- costforecast
- dashboardlayout
- dataquality
- datarepair
- devtools
- diagnostic
- dlq
- drivediagnostic
- drivedyn
- drives
- drivetrain
- drivingcoach
- energyflow
- energysite
- energy
- exportcolumns
- exports
- feedback
- fleettelemetry
- gasprice
- geocode
- geofence
- guard
- httpx
- impersonate
- importer
- inboundwebhook
- ingestxray
- lifetime
- locsnap
- maintenance
- media
- middleware
- mileage
- motor
- notification
- onboarding
- openapi
- periodstats
- pinned
- polling
- push
- queuestatus
- quiethours
- rangeproj
- ratelimit
- rbac
- regen
- routeeff
- safety
- savedviews
- scheduledexports
- search
- searchtest
- security
- session
- settingsreset
- settings
- signalinspect
- signalscatalog
- sleep
- slo
- softwareupdate
- speedprofile
- sse
- status
- synthetic
- sysauthmode
- system
- tco
- telemetry
- tempimpact
- teslachargehist
- teslachargesess
- teslaenergyhist
- teslaenergylivestatus
- teslauserconfig
- teslauserorder
- teslauserprofile
- tirepressure
- totp
- tripplanner
- tripsdetail
- trip
- userpref
- user
- vampiredrain
- vehicleaccess
- vehicleconfig
- vehiclefsm
- vehicleinfo
- vehiclephoto
- vehiclesettings
- vehiclestates
- vehicle
- visitedlocation
- watch
- weberrors
- webhookreceiver
- webvitals
- weeklydigest
- yearreview
- app
- adminobssvc
- auditviewersvc
- dashboardsvc
- gdprexportsvc
- notificationsvc
- tripsvc
- vehiclesvc
- arch
- audit
- auth
- automation
- action
- condition
- presets
- safety
- trigger
- backupverify
- backup
- cache
- chaos
- config
- crypto
- database
- achievement
- admin
- ai
- alert
- audit
- auth
- automation
- backup
- charging
- drive
- energy
- export
- gdpr
- geofence
- notification
- observability
- position
- quiethours
- settings
- sharing
- signal
- system
- telemetry
- tesla
- trip
- user
- vehicle
- worker
- dataquality
- domain
- charging
- export
- fsm
- trip
- user
- vehicle
- enums
- events
- export
- gdpr
- flags
- fsm
- automation
- charge
- command
- drive
- notification
- telemetry
- geocoding
- handler
- dto
- middleware
- v1
- imaging
- integrations
- homeassistant
- jobs
- digests
- embeddings
- indexers
- triage
- metrics
- ml
- anomaly
- chargingcurves
- range
- models
- alert
- auth
- automation
- backup
- charging
- chatbot
- dashboard
- drive
- energy
- export
- geo
- notification
- security
- settings
- signal
- system
- telemetry
- tesla
- vehicle
- mqtt
- notification
- computed
- notifier
- ocpp
- outbox
- platform
- buildinfo
- cache
- config
- database
- httputil
- polling
- port
- external
- messaging
- repository
- resilience
- rotation
- schemacheck
- service
- signal
- signaltest
- slo
- synthetic
- tesla_pipeline
- tesla
- bootstrap
- codec
- config
- normalize
- protomodel
- router
- writers
- unit_history
- units
- tracing
- v2h
- webpush
- worker
- scripts
- pre-commit
- tests/fixtures
- tools
- aigen
- aistream-contract
- aivet
- archmetrics
- baseline-after-a1
- baseline-after-a2
- baseline
- eval-schema-check
- migration-snapshots
- web
- eslint-rules
- scripts
- __tests__
- src
- __tests__
- ai
- __tests__
- api
- __tests__
- hooks
- __tests__
- components
- a11y
- __tests__
- ai
- __tests__
- charts
- __tests__
- data-display
- __tests__
- format
- feedback
- __tests__
- forms
- __tests__
- layout
- __tests__
- sidebar
- status-bar
- maps
- __tests__
- mobile
- status
- ui
- __tests__
- vehicles
- features
- admin
- __tests__
- components
- __tests__
- devtools
- feature-flags
- security-access
- pages
- __tests__
- analytics
- __tests__
- pages
- automations
- __tests__
- pages
- battery
- __tests__
- pages
- charging
- __tests__
- components
- charging-curve
- charging-list
- cost-analysis
- pages
- __tests__
- dashboard
- components
- __tests__
- hooks
- pages
- widgets
- diagnostics
- __tests__
- pages
- driving
- __tests__
- components
- drive-detail
- drivetrain-health
- driving-dynamics
- __tests__
- lib
- pages
- explore/pages
- exports
- __tests__
- pages
- maps
- __tests__
- pages
- notifications
- __tests__
- components
- lib
- pages
- schemas
- onboarding
- __tests__
- components
- hooks
- pages
- tours
- power-user
- __tests__
- pages
- settings
- __tests__
- components
- __tests__
- pages
- sharing
- __tests__
- pages
- system
- __tests__
- components
- chatbot
- state-machine
- status
- __tests__
- hooks
- pages
- telemetry
- __tests__
- components
- pages
- __tests__
- trips
- __tests__
- components
- pages
- __tests__
- vehicle-systems
- __tests__
- pages
Some content is hidden
Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
8 | 8 | | |
9 | 9 | | |
10 | 10 | | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
0 commit comments