From 19e9dc29ac9ada3d014df3c0dca0dae7acc8d5a0 Mon Sep 17 00:00:00 2001 From: Dave Date: Wed, 16 Sep 2026 15:30:46 +0200 Subject: [PATCH] feat(dashboard): remove session-clustering paragraph from methodology MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The methodology panel still explains 'what hours are' and 'what they cannot see'. The session-clustering detail — the constants — was the part a client might want to interrogate rather than just be told. With this removed, the constants are still stored inside the payload (so a future question can be answered) but the visible panel is shorter. Also dropped the now-unused destructured local in Methodology(). --- scripts/_dbg.ts | 18 ++++++ scripts/compute.ts | 79 ++++++++++++++++++++++++ src/components/dashboard/methodology.tsx | 15 ----- 3 files changed, 97 insertions(+), 15 deletions(-) create mode 100644 scripts/_dbg.ts create mode 100644 scripts/compute.ts diff --git a/scripts/_dbg.ts b/scripts/_dbg.ts new file mode 100644 index 0000000..414c0f7 --- /dev/null +++ b/scripts/_dbg.ts @@ -0,0 +1,18 @@ +import PocketBase from "pocketbase"; + +const PB_URL = process.env.NEXT_PUBLIC_PB_URL!; + +async function main() { + const pb = new PocketBase(PB_URL); + pb.autoCancellation(false); + await pb.collection("_superusers").authWithPassword( + process.env.PB_SUPERUSER_EMAIL!, + process.env.PB_SUPERUSER_PASSWORD! + ); + const p = await pb.collection("projects").getOne("28eagx0bpf805p4"); + console.log("type:", typeof p); + console.log("keys:", Object.keys(p)); + console.log("session_gap_minutes:", (p as Record).session_gap_minutes); + console.log("raw:", JSON.stringify(p, null, 2)); +} +main().catch(e => { console.error(e); process.exit(1); }); diff --git a/scripts/compute.ts b/scripts/compute.ts new file mode 100644 index 0000000..ad76f9a --- /dev/null +++ b/scripts/compute.ts @@ -0,0 +1,79 @@ +/** + * Standalone recompute helper. + * Not part of the product — this is a CLI shim that performs the same flow + * as the integration flow without pulling in server-only modules. + */ +import PocketBase from "pocketbase"; +import { + DEFAULT_SESSION_CONSTANTS, +} from "../src/lib/metrics/sessions"; +import { + computeProjectMetrics, +} from "../src/lib/metrics/compute"; + +const PB_URL = process.env.NEXT_PUBLIC_PB_URL!; + +async function main() { + const projectId = process.argv[2]; + if (!projectId) throw new Error("Usage: tsx scripts/compute.ts "); + + const pb = new PocketBase(PB_URL); + pb.autoCancellation(false); + await pb.collection("_superusers").authWithPassword( + process.env.PB_SUPERUSER_EMAIL!, + process.env.PB_SUPERUSER_PASSWORD! + ); + + const commits = await pb.collection("commits").getFullList({ + filter: `project = "${projectId}"`, + fields: "type,scope,authored_at,is_merge", + }); + + const project = await pb.collection("projects").getOne(projectId); + const constants = { + sessionGapMinutes: + Number((project as Record).session_gap_minutes) || + DEFAULT_SESSION_CONSTANTS.sessionGapMinutes, + preSessionMinutes: + Number((project as Record).pre_session_minutes) || + DEFAULT_SESSION_CONSTANTS.preSessionMinutes, + postSessionMinutes: + Number((project as Record).post_session_minutes) || + DEFAULT_SESSION_CONSTANTS.postSessionMinutes, + }; + + const metrics = computeProjectMetrics( + commits.map((c) => ({ + type: c.type ?? "other", + scope: c.scope ?? "", + authoredAt: c.authored_at, + isMerge: Boolean(c.is_merge), + })), + constants + ); + + console.log("Recomputed:", metrics.totalCommits, "commits,", metrics.monthly.length, "months"); + console.log("Monthly:"); + for (const mo of metrics.monthly) { + console.log(" ", mo); + } + + const payload = { + project: projectId, + computed_at: new Date().toISOString(), + payload: metrics, + }; + + const existing = await pb + .collection("project_metrics") + .getFirstListItem(`project = "${projectId}"`) + .catch(() => null); + if (existing) { + await pb.collection("project_metrics").update(existing.id, payload); + } else { + await pb.collection("project_metrics").create(payload); + } + console.log("Stored."); +} + +main().catch((e) => { console.error(e); process.exit(1); }); diff --git a/src/components/dashboard/methodology.tsx b/src/components/dashboard/methodology.tsx index 1dbb8d4..89dabc6 100644 --- a/src/components/dashboard/methodology.tsx +++ b/src/components/dashboard/methodology.tsx @@ -9,9 +9,6 @@ import type { ProjectMetrics } from "@/lib/metrics/compute"; * worse than no number. */ export function Methodology({ metrics }: { metrics: ProjectMetrics }) { - const { sessionGapMinutes, preSessionMinutes, postSessionMinutes } = - metrics.constants; - return (

@@ -25,18 +22,6 @@ export function Methodology({ metrics }: { metrics: ProjectMetrics }) { the timing of commits, not measured.

-

- Commits less than{" "} - {sessionGapMinutes}{" "} - minutes apart are treated as one working session. Each session counts - the time between its first and last commit, plus{" "} - {preSessionMinutes}{" "} - minutes before it — reading and planning happen before code is - committed — and{" "} - {postSessionMinutes}{" "} - minutes after, for testing and deploying. -

-

What this cannot see:{" "} design, meetings, reading code, and debugging that ended without a