From 8087ba4db0fb41af1ee67e4e805a2a4712973970 Mon Sep 17 00:00:00 2001 From: Bryan Van Deusen Date: Tue, 4 Aug 2026 10:41:49 -0400 Subject: [PATCH] feat(design): a project reports drift in its own recorded components MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The check has taken a project id since it was written — check_snippets_against_ system(user_id, design_system_id, project_id=0), and the route has always read ?project_id=. Nothing on the frontend ever passed one and no project-side surface existed, so the capability shipped and stayed unreachable. A Design tab on the project, beside Systems and Rules, reporting three things per snippet: no such token var(--x) the system doesn't declare. Renders as NOTHING — no error, no failing test, just an element quietly unstyled. Leads for that reason. defines its own a component minting a custom property instead of reaching for the shared one. This is the DRY finding and the reason the surface exists: the codebase re-solving a solved problem, one component at a time, visible only when someone changes the shared value and half the components don't move. write the token a literal the sheet says to stop writing, paired with what to write instead. Three empty states, kept distinct, because collapsing them is how a check comes to sit dead: no design system bound, no snippets recorded (nothing was checked), and checked-and-clean. The last one says how many were checked. Bound to the SAVED pointer rather than the sidebar picker's draft, so an unsaved change can't make the tab report against a system the project isn't using. Scope is recorded code, per the operator: snippets are what Scribe holds, and a repository's own sources are checked where they live, by that project's CI. Step 3 of milestone #274. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01UaYUaouG9jjhATyuxCKrQs --- frontend/src/api/designSystems.ts | 14 +- frontend/src/components/ProjectDesignTab.vue | 234 +++++++++++++++++++ frontend/src/views/ProjectView.vue | 16 +- 3 files changed, 260 insertions(+), 4 deletions(-) create mode 100644 frontend/src/components/ProjectDesignTab.vue diff --git a/frontend/src/api/designSystems.ts b/frontend/src/api/designSystems.ts index e3d7f21..7641ccf 100644 --- a/frontend/src/api/designSystems.ts +++ b/frontend/src/api/designSystems.ts @@ -200,6 +200,14 @@ export interface SnippetCheck { findings: SnippetFinding[]; } -/** Which recorded snippets disagree with this design system's sheet. */ -export const checkSnippets = (id: number) => - apiGet(`/api/design-systems/${id}/snippet-check`); +/** Which recorded snippets disagree with this design system's sheet. + * + * `projectId` narrows to the snippets one project owns — which is how a + * project asks about its OWN code. Omit it to check every project, which is + * the right default from the system's side: a component recorded elsewhere + * still has to use the same tags. */ +export const checkSnippets = (id: number, projectId?: number) => + apiGet( + `/api/design-systems/${id}/snippet-check` + + (projectId ? `?project_id=${projectId}` : ""), + ); diff --git a/frontend/src/components/ProjectDesignTab.vue b/frontend/src/components/ProjectDesignTab.vue new file mode 100644 index 0000000..01f9bd4 --- /dev/null +++ b/frontend/src/components/ProjectDesignTab.vue @@ -0,0 +1,234 @@ + + + + + diff --git a/frontend/src/views/ProjectView.vue b/frontend/src/views/ProjectView.vue index df691ba..538c01d 100644 --- a/frontend/src/views/ProjectView.vue +++ b/frontend/src/views/ProjectView.vue @@ -7,6 +7,7 @@ import { useTasksStore } from "@/stores/tasks"; import { relativeTime } from "@/composables/useRelativeTime"; import { renderMarkdown } from "@/utils/markdown"; import ShareDialog from "@/components/ShareDialog.vue"; +import ProjectDesignTab from "@/components/ProjectDesignTab.vue"; import ProjectRulesTab from "@/components/rules/ProjectRulesTab.vue"; import SystemsSection from "@/components/SystemsSection.vue"; import { @@ -108,7 +109,7 @@ async function confirmStartPlanning() { const saving = ref(false); const error = ref(null); -const activeTab = ref<"tasks" | "notes" | "systems" | "rules">("tasks"); +const activeTab = ref<"tasks" | "notes" | "systems" | "rules" | "design">("tasks"); const tasks = ref([]); const notes = ref([]); @@ -570,6 +571,9 @@ async function confirmDelete() { + @@ -784,6 +788,16 @@ async function confirmDelete() { + + +