Skip to content

Commit da59e12

Browse files
atulmguptaCopilot
andcommitted
chore(phase-43a): orphan hook disposition (useAlerts, useDashboardLayouts)
Adds web/src/api/hooks/orphan-allowlist.ts documenting two intentional ORPHAN waivers per phase-43a/0001: - useAlerts.ts: domain-named re-export shim from Phase-45/32 (PR #61); call sites still import from useNotifications. Adoption pending. - useDashboardLayouts.ts: named-layout library hooks with LIVE backend routes (/dashboard/layouts/*) but the UI integration with LayoutSwitcher / DashboardPage is not yet wired (out of scope for this prompt; deferred to a future re-mount prompt). Plus a documented baseline-fix sweep for 8 pre-existing lint failures (4 ESLint no-extra-semi/unused-disable, 4 audit:query-signal AbortSignal threading) so cd web && npm run lint passes end-to-end. Each baseline fix is a single-line edit unrelated to orphan disposition; explicit scope expansion documented in the prompt log per Honesty Covenant 8. See .github/prompts/db-refactor/logs/phase-43a-0001-orphans.log for the full audit evidence, design rationale, and gate output. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 0ed444c commit da59e12

8 files changed

Lines changed: 505 additions & 10 deletions

File tree

.github/prompts/db-refactor/logs/phase-43a-0001-orphans.log

Lines changed: 416 additions & 0 deletions
Large diffs are not rendered by default.

web/src/api/__tests__/sseClient.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -218,7 +218,7 @@ describe('subscribeSignals', () => {
218218

219219
afterEach(() => {
220220
if (originalEventSource) {
221-
;(globalThis as { EventSource: unknown }).EventSource = originalEventSource
221+
(globalThis as { EventSource: unknown }).EventSource = originalEventSource
222222
} else {
223223
delete (globalThis as { EventSource?: unknown }).EventSource
224224
}

web/src/api/hooks/__tests__/useSSE.test.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ describe('useSignalChangeStream', () => {
8383
})
8484

8585
afterEach(() => {
86-
;(global as { EventSource: unknown }).EventSource = originalEventSource
86+
(global as { EventSource: unknown }).EventSource = originalEventSource
8787
})
8888

8989
it('subscribes to the signal_change channel and surfaces typed events', () => {
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
/**
2+
* orphan-allowlist — intentional ORPHAN hook waiver list (Phase-43a/0001).
3+
*
4+
* The phase-43 hook coverage audit (`phase-43-0080-hook-coverage-audit.log`)
5+
* flags any file under `web/src/api/hooks/` whose exported `use*` symbols
6+
* have ZERO production consumers. By default an ORPHAN status BLOCKS the
7+
* audit. Files listed here are exempted: the audit treats listed entries as
8+
* PASS and continues without erroring.
9+
*
10+
* Honesty Covenant rule 11 — "no dead code retention" — REQUIRES that any
11+
* file added here have a documented reason and (where applicable) a link
12+
* to a backlog issue tracking the future mount. Adding an entry without a
13+
* reason or without committing to a future cleanup IS dead-code retention
14+
* by stealth and SHOULD be rejected in code review.
15+
*
16+
* To remove an entry: either (a) wire the hook into a real consumer, or
17+
* (b) delete the hook file. Both choices are preferable to growing the
18+
* allowlist.
19+
*
20+
* Each entry's `file` is the bare filename relative to `web/src/api/hooks/`
21+
* (no leading path, no `.ts` extension assumed by callers — match exactly
22+
* as the hook coverage audit emits the name).
23+
*/
24+
25+
export interface OrphanWaiver {
26+
/** Bare filename, e.g. 'useAlerts.ts'. Must match the audit's file column. */
27+
readonly file: string;
28+
/** Why this hook is allowed to ship without a production consumer. */
29+
readonly reason: string;
30+
/**
31+
* Tracking note for the future-mount work. Use a GitHub issue URL when
32+
* one exists; a TODO marker is acceptable while the backlog item is
33+
* still being filed.
34+
*/
35+
readonly tracking: string;
36+
}
37+
38+
export const INTENTIONAL_ORPHANS: readonly OrphanWaiver[] = [
39+
{
40+
file: 'useAlerts.ts',
41+
reason:
42+
'Domain-named re-export shim introduced in Phase-45 (PR #61, commit a2010406). ' +
43+
'Re-exports alert-specific symbols from useNotifications so future call sites ' +
44+
'can import from @/api/hooks/useAlerts without pulling notification-channel ' +
45+
'types. The shim has no callers today because every consumer (AlertsPage, ' +
46+
'AlertFeedWidget) still imports directly from useNotifications; the shim is ' +
47+
'kept available as the migration path.',
48+
tracking:
49+
'TODO(phase-43a): file backlog issue to migrate alert imports from ' +
50+
"`@/api/hooks/useNotifications` to `@/api/hooks/useAlerts` and remove this " +
51+
'waiver entry once migration is complete.',
52+
},
53+
{
54+
file: 'useDashboardLayouts.ts',
55+
reason:
56+
'Named-layout library hooks (useNamedDashboardLayouts / useCreateDashboardLayout / ' +
57+
'useUpdateDashboardLayout / useDeleteDashboardLayout / useApplyDashboardLayout) ' +
58+
'introduced in Phase-40/30 (commit 8009c98e2). The backend routes at ' +
59+
'/api/v1/dashboard/layouts/* are LIVE (see internal/api/router.go L963-970 and ' +
60+
'internal/api/dashboard_layout_handler.go) but the LayoutSwitcher UI in ' +
61+
'web/src/features/dashboard/components/LayoutSwitcher.tsx still operates on ' +
62+
'local-state SavedDashboard[] passed via props, not on the per-row backend ' +
63+
'library. The hooks are the missing client half of a half-finished feature.',
64+
tracking:
65+
'TODO(phase-43a): file backlog issue to wire LayoutSwitcher save-as-preset / ' +
66+
'apply-preset flow into the named-layout hooks. Out of scope for this prompt ' +
67+
"because integration touches features/dashboard/hooks/useDashboardLayout.ts " +
68+
'(839 lines), LayoutSwitcher.tsx, and DashboardPage.tsx beyond the prompt budget.',
69+
},
70+
] as const;
71+
72+
/**
73+
* True iff the given hook filename is intentionally allowed to be ORPHAN.
74+
*
75+
* @param file - bare filename relative to `web/src/api/hooks/`, e.g. `useAlerts.ts`
76+
*/
77+
export function isIntentionalOrphan(file: string): boolean {
78+
return INTENTIONAL_ORPHANS.some((entry) => entry.file === file);
79+
}

web/src/api/hooks/useFleetTelemetry.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,10 @@ export const fleetTelemetryKeys = {
3535
export function useFleetTelemetryCoverage() {
3636
return useQuery({
3737
queryKey: fleetTelemetryKeys.coverage,
38-
queryFn: async (): Promise<FleetTelemetryCoverageResponse> => {
38+
queryFn: async ({ signal }): Promise<FleetTelemetryCoverageResponse> => {
3939
const raw = await request<FleetTelemetryCoverageResponse>(
4040
'/tesla/fleet-telemetry/coverage',
41+
{ signal },
4142
)
4243
return {
4344
categories: raw.categories ?? [],

web/src/api/hooks/useSignals.ts

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -210,8 +210,8 @@ function normalizeDescriptor(raw: RawAvailableResponse['signals'] extends (infer
210210
export function useAvailableSignals(vehicleId: number) {
211211
return useQuery({
212212
queryKey: signalKeys.available(vehicleId),
213-
queryFn: async (): Promise<AvailableSignalsResponse> => {
214-
const raw = await request<RawAvailableResponse>(`/signals/${vehicleId}/available`)
213+
queryFn: async ({ signal }): Promise<AvailableSignalsResponse> => {
214+
const raw = await request<RawAvailableResponse>(`/signals/${vehicleId}/available`, { signal })
215215
const signals = (raw.signals ?? []).map(normalizeDescriptor)
216216
return {
217217
vehicle_id: raw.vehicle_id,
@@ -242,8 +242,8 @@ interface RawLiveResponse {
242242
export function useLiveSignals(vehicleId: number) {
243243
return useQuery({
244244
queryKey: signalKeys.live(vehicleId),
245-
queryFn: async (): Promise<LiveSignalsResponse> => {
246-
const raw = await request<RawLiveResponse>(`/signals/${vehicleId}/live`)
245+
queryFn: async ({ signal }): Promise<LiveSignalsResponse> => {
246+
const raw = await request<RawLiveResponse>(`/signals/${vehicleId}/live`, { signal })
247247
const signals: Record<string, SignalEnvelope> = {}
248248
for (const [field, env] of Object.entries(raw.signals ?? {})) {
249249
signals[field] = normalizeEnvelope(env ?? null)
@@ -286,7 +286,7 @@ export function useSignalHistory(
286286
const hours = range.hours ?? 24
287287
return useQuery({
288288
queryKey: signalKeys.history(vehicleId, signalName, hours, range.from, range.to),
289-
queryFn: async (): Promise<SignalHistoryResponseTyped> => {
289+
queryFn: async ({ signal }): Promise<SignalHistoryResponseTyped> => {
290290
const usp = new URLSearchParams()
291291
if (range.from && range.to) {
292292
usp.set('from', range.from)
@@ -300,6 +300,7 @@ export function useSignalHistory(
300300
const qs = usp.toString()
301301
const raw = await request<RawHistoryResponse>(
302302
`/signals/${vehicleId}/${signalName}/history${qs ? `?${qs}` : ''}`,
303+
{ signal },
303304
)
304305
const data = (raw.data ?? []).map((row) => normalizeEnvelope(row))
305306
return {

web/src/features/vehicle-systems/pages/ClimateControlPage.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -381,7 +381,6 @@ export default function ClimateControlPage() {
381381
})),
382382
// Track the primitive `tempUnit` instead of the closure `convertTemp`
383383
// so non-temperature settings churn doesn't invalidate the memo.
384-
// eslint-disable-next-line react-hooks/exhaustive-deps
385384
[chronoHistory, tempUnit],
386385
);
387386

web/src/features/vehicle-systems/pages/TirePressurePage.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -234,7 +234,6 @@ export default function TirePressurePage() {
234234
// unitPrefs.pressure is the only relevant primitive dep — depending on
235235
// the closure-captured `toDisplayPressure` would also work but referencing
236236
// the primitive keeps the dep list stable for memo invalidation.
237-
// eslint-disable-next-line react-hooks/exhaustive-deps
238237
}, [history, unitPrefs.pressure]);
239238

240239
/* ---- Table columns ---- */

0 commit comments

Comments
 (0)