docs(6208): ProjectView, KnowledgeView, SnippetListView and components.css comments drop citations and stories
CI & Build / Python lint (push) Successful in 3s
CI & Build / Plugin hooks (push) Successful in 13s
CI & Build / TypeScript typecheck (push) Successful in 53s
CI & Build / integration (push) Successful in 1m10s
CI & Build / Python tests (push) Successful in 1m54s
CI & Build / Build & push image (push) Successful in 52s
CI & Build / Python lint (push) Successful in 3s
CI & Build / Plugin hooks (push) Successful in 13s
CI & Build / TypeScript typecheck (push) Successful in 53s
CI & Build / integration (push) Successful in 1m10s
CI & Build / Python tests (push) Successful in 1m54s
CI & Build / Build & push image (push) Successful in 52s
components.css says why shared recipes live there without the five-copy .btn-primary count; KnowledgeView's editor-routing and chip comments state the present reason instead of the bug that once routed snippets wrong. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -3,11 +3,10 @@
|
|||||||
*
|
*
|
||||||
* WHY THIS FILE EXISTS
|
* WHY THIS FILE EXISTS
|
||||||
*
|
*
|
||||||
* `.btn-primary` was defined five times, in five scoped stylesheets, and all
|
* A recipe copied into several scoped stylesheets drifts — paddings, font
|
||||||
* five had drifted: three paddings, three font sizes, three disabled opacities,
|
* sizes, disabled states — and nothing detects it, because a scoped duplicate
|
||||||
* and one view with no disabled style at all (#2273). Nothing detected that,
|
* is invisible to every tool: it isn't a rule violation, isn't a broken
|
||||||
* because a scoped duplicate is invisible to every tool — it isn't a rule
|
* reference, and isn't a recorded snippet. Shared recipes live here instead.
|
||||||
* violation, isn't a broken reference, and isn't a recorded snippet.
|
|
||||||
*
|
*
|
||||||
* GEOMETRY LIVES HERE. Every value is a design-system token, so a palette or
|
* GEOMETRY LIVES HERE. Every value is a design-system token, so a palette or
|
||||||
* scale change moves the buttons rather than stranding a copy that no longer
|
* scale change moves the buttons rather than stranding a copy that no longer
|
||||||
@@ -301,9 +300,7 @@
|
|||||||
/* ── Page container ─────────────────────────────────────────────────────────
|
/* ── Page container ─────────────────────────────────────────────────────────
|
||||||
The one wrapper a top-level view sits in: page width from the layout
|
The one wrapper a top-level view sits in: page width from the layout
|
||||||
tokens, centred, clipped horizontally so a wide child (a kanban, a table)
|
tokens, centred, clipped horizontally so a wide child (a kanban, a table)
|
||||||
scrolls inside itself instead of the page. ProjectListView, ProjectView and
|
scrolls inside itself instead of the page. */
|
||||||
SnippetListView each carried this rule under their own name until #2903
|
|
||||||
(milestone 299). */
|
|
||||||
.page-container {
|
.page-container {
|
||||||
max-width: var(--fs-layout-page-max);
|
max-width: var(--fs-layout-page-max);
|
||||||
margin: 2rem auto;
|
margin: 2rem auto;
|
||||||
@@ -311,12 +308,11 @@
|
|||||||
overflow-x: clip;
|
overflow-x: clip;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ── Form input (fs-surfaces, snippet #2336) ────────────────────────────────
|
/* ── Form input (fs-surfaces) ───────────────────────────────────────────────
|
||||||
Inputs sit DARKER than the page they're on — an inset well rather than a
|
Inputs sit DARKER than the page they're on — an inset well rather than a
|
||||||
raised panel; that inversion is what makes a field read as writable. The
|
raised panel; that inversion is what makes a field read as writable. The
|
||||||
design system's recipe, verbatim; width/box-sizing stay the caller's
|
design system's recipe, verbatim; width/box-sizing stay the caller's
|
||||||
(an inline select and a full-width textarea differ there). Three scoped
|
(an inline select and a full-width textarea differ there). */
|
||||||
copies of an older input recipe were folded into this in #2903. */
|
|
||||||
.fs-input {
|
.fs-input {
|
||||||
background: var(--fs-surface-page);
|
background: var(--fs-surface-page);
|
||||||
border: var(--fs-border);
|
border: var(--fs-border);
|
||||||
@@ -331,8 +327,8 @@
|
|||||||
.fs-input:focus { outline: none; box-shadow: var(--fs-focus-ring); }
|
.fs-input:focus { outline: none; box-shadow: var(--fs-focus-ring); }
|
||||||
.fs-input:disabled { opacity: var(--fs-disabled-opacity); cursor: not-allowed; }
|
.fs-input:disabled { opacity: var(--fs-disabled-opacity); cursor: not-allowed; }
|
||||||
|
|
||||||
/* Page scaffold + feedback text recipes (milestone 302, note 2917): name
|
/* Page scaffold + feedback text recipes: families that are one recipe
|
||||||
families the consumer map showed to be one recipe living in many views.
|
living in many views.
|
||||||
A view keeps only its deviation as a scoped remainder/override. */
|
A view keeps only its deviation as a scoped remainder/override. */
|
||||||
.page-header {
|
.page-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -355,9 +351,8 @@
|
|||||||
/* --- usage badge ----------------------------------------------------------
|
/* --- usage badge ----------------------------------------------------------
|
||||||
"surfaced N×, opened M×" on a list row, for any record kind the retrieval
|
"surfaced N×, opened M×" on a list row, for any record kind the retrieval
|
||||||
surfaces can choose: snippets and notes from note_usage_events, rules from
|
surfaces can choose: snippets and notes from note_usage_events, rules from
|
||||||
rule_usage_events. Promoted here from SnippetListView's scoped block when
|
rule_usage_events. Shared here because a second scoped copy is how a
|
||||||
the rule list needed the same chip (milestone 333 step 5) — a second scoped
|
duplicated CSS family starts.
|
||||||
copy is how the ninth duplicated CSS family starts (#3207).
|
|
||||||
|
|
||||||
Geometry and colour only. A view keeps its own spacing as a scoped
|
Geometry and colour only. A view keeps its own spacing as a scoped
|
||||||
remainder, the way it does for every other recipe in this file. */
|
remainder, the way it does for every other recipe in this file. */
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ interface KnowledgeItem {
|
|||||||
created_at: string;
|
created_at: string;
|
||||||
updated_at: string;
|
updated_at: string;
|
||||||
/** Surfaced-vs-opened counts, zero-filled by the route for EVERY row, so
|
/** Surfaced-vs-opened counts, zero-filled by the route for EVERY row, so
|
||||||
* "never surfaced" is a value here rather than a missing field (#4230).
|
* "never surfaced" is a value here rather than a missing field.
|
||||||
* `UsageBadge` renders nothing at all below one surfacing. */
|
* `UsageBadge` renders nothing at all below one surfacing. */
|
||||||
usage?: RecordUsage | null;
|
usage?: RecordUsage | null;
|
||||||
// Set only when another user owns this record — their suggestion, not one of
|
// Set only when another user owns this record — their suggestion, not one of
|
||||||
@@ -69,10 +69,9 @@ interface KnowledgeItem {
|
|||||||
// a task KIND (`task` for any, else issue / spike) — because that is what this
|
// a task KIND (`task` for any, else issue / spike) — because that is what this
|
||||||
// feed actually holds.
|
// feed actually holds.
|
||||||
//
|
//
|
||||||
// `plan` is still a valid facet at the API, for the 90 legacy plan-tasks, but
|
// `plan` is still a valid facet at the API, for legacy plan-tasks, but it has
|
||||||
// it has no chip: retired in 0066, it kept a chip of its own for longer than
|
// no chip: plans are milestones now. Those rows are still reachable under
|
||||||
// `issue` — 17% of every task here — went without one (#3128). Those rows are
|
// Tasks, wearing a Plan badge.
|
||||||
// still reachable under Tasks, wearing a Plan badge.
|
|
||||||
type Facet = "" | "note" | "task" | "issue" | "spike" | "snippet" | "process" | "lesson";
|
type Facet = "" | "note" | "task" | "issue" | "spike" | "snippet" | "process" | "lesson";
|
||||||
|
|
||||||
// The facets that select TASKS. Kinds are subsets of `task`, so any of them
|
// The facets that select TASKS. Kinds are subsets of `task`, so any of them
|
||||||
@@ -93,7 +92,7 @@ const FACET_CHIPS: [Exclude<Facet, "">, string][] = [
|
|||||||
// The sweep is cross-cutting — a note that has gone false does not care which
|
// The sweep is cross-cutting — a note that has gone false does not care which
|
||||||
// facet it sits under — so it REPLACES the browse list rather than filtering
|
// facet it sits under — so it REPLACES the browse list rather than filtering
|
||||||
// it. Filtering would mean the answer depended on which chip was active, which
|
// it. Filtering would mean the answer depended on which chip was active, which
|
||||||
// is the under-reporting the sweep exists to prevent (milestone 317 step 4).
|
// is the under-reporting the sweep exists to prevent.
|
||||||
const sweepActive = ref(false);
|
const sweepActive = ref(false);
|
||||||
|
|
||||||
// ─── Filter state ─────────────────────────────────────────────────────────────
|
// ─── Filter state ─────────────────────────────────────────────────────────────
|
||||||
@@ -201,8 +200,8 @@ const items = ref<KnowledgeItem[]>([]);
|
|||||||
|
|
||||||
// True only when a search actually returned matched passages — not merely when
|
// True only when a search actually returned matched passages — not merely when
|
||||||
// the box has text in it. A keyword-only result set, or an embedder that is
|
// the box has text in it. A keyword-only result set, or an embedder that is
|
||||||
// down, carries `body_opening` rows, and claiming otherwise would be the same
|
// down, carries `body_opening` rows, and labelling those as matched passages
|
||||||
// species of lie this whole change is about (#4243).
|
// would misstate why the row is there.
|
||||||
const showsMatchedPassages = computed(
|
const showsMatchedPassages = computed(
|
||||||
() =>
|
() =>
|
||||||
searchQuery.value.trim().length > 0 &&
|
searchQuery.value.trim().length > 0 &&
|
||||||
@@ -351,12 +350,8 @@ function isOverdue(item: KnowledgeItem): boolean {
|
|||||||
return new Date(item.due_date) < new Date(new Date().toDateString());
|
return new Date(item.due_date) < new Date(new Date().toDateString());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Each record kind opens in ITS OWN editor. A snippet used to fall through to
|
// Each record kind opens in ITS OWN editor: the note editor cannot edit a
|
||||||
// /notes/:id, whose save is a plain PATCH of the body — which left the snippet's
|
// snippet's signature, language or locations, so it is not the way in. Processes stay here on
|
||||||
// derived `data` mirror describing the previous version (#3128). The service now
|
|
||||||
// recomposes the mirror either way, so this is no longer the guard; it is simply
|
|
||||||
// that the note editor cannot edit a snippet's signature, language or locations,
|
|
||||||
// and offering it as the way in was always wrong. Processes stay here on
|
|
||||||
// purpose: they have no editor of their own and the note editor knows the type.
|
// purpose: they have no editor of their own and the note editor knows the type.
|
||||||
function openItem(item: KnowledgeItem) {
|
function openItem(item: KnowledgeItem) {
|
||||||
if (item.note_type === 'task') {
|
if (item.note_type === 'task') {
|
||||||
@@ -666,7 +661,7 @@ onUnmounted(() => {
|
|||||||
class="shared-tag"
|
class="shared-tag"
|
||||||
:title="`Shared by ${item.owner ?? 'another user'} — their record, not yours`"
|
:title="`Shared by ${item.owner ?? 'another user'} — their record, not yours`"
|
||||||
>by {{ item.owner ?? "another user" }}</span>
|
>by {{ item.owner ?? "another user" }}</span>
|
||||||
<!-- Surfaced-vs-opened (#4230). This is the only list in the UI
|
<!-- Surfaced-vs-opened. This is the only list in the UI
|
||||||
that browses notes and lessons, so it is the only place
|
that browses notes and lessons, so it is the only place
|
||||||
those two kinds can show the counter at all. The advice is
|
those two kinds can show the counter at all. The advice is
|
||||||
looked up per row because this feed is mixed — the remedy
|
looked up per row because this feed is mixed — the remedy
|
||||||
@@ -1015,9 +1010,7 @@ onUnmounted(() => {
|
|||||||
.badge--note { background: color-mix(in srgb, var(--fs-accent) 15%, transparent); color: #7A6DA8; }
|
.badge--note { background: color-mix(in srgb, var(--fs-accent) 15%, transparent); color: #7A6DA8; }
|
||||||
.badge--task { background: rgba(212,160,23,0.15); color: #fbbf24; }
|
.badge--task { background: rgba(212,160,23,0.15); color: #fbbf24; }
|
||||||
.badge--plan { background: rgba(99,102,241,0.18); color: #818cf8; }
|
.badge--plan { background: rgba(99,102,241,0.18); color: #818cf8; }
|
||||||
/* Snippet and process are NEUTRAL on purpose. Both were unstyled — and the
|
/* Snippet and process are NEUTRAL on purpose. Giving them hues would put a third and fourth colour
|
||||||
snippet had no label either, so all 90 of them rendered an empty chip in
|
|
||||||
this feed (#3128). Giving them hues would put a third and fourth colour
|
|
||||||
beside KindBadge's warm/cool pair on the same card; a record type that
|
beside KindBadge's warm/cool pair on the same card; a record type that
|
||||||
isn't an alarm reads better as plain. Standard body pair, so the contrast
|
isn't an alarm reads better as plain. Standard body pair, so the contrast
|
||||||
is the one the palette already guarantees. */
|
is the one the palette already guarantees. */
|
||||||
@@ -1158,11 +1151,9 @@ onUnmounted(() => {
|
|||||||
border-bottom: 1px solid var(--fs-border-color);
|
border-bottom: 1px solid var(--fs-border-color);
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
/* RESTORED (#2444). The panel is a flex COLUMN and its header is
|
/* The panel is a flex COLUMN and its header is `flex-shrink: 0`, so this is
|
||||||
`flex-shrink: 0`, so this is the item that takes the remaining height — and
|
the item that takes the remaining height — without it the `height: 100%`
|
||||||
without it the `height: 100%` below resolves against `auto` and does
|
below resolves against `auto` and does nothing. `min-height: 0` is the companion that lets a flex item shrink under
|
||||||
nothing, which made the comment underneath a spec for a rule that could not
|
|
||||||
work. `min-height: 0` is the companion that lets a flex item shrink under
|
|
||||||
its content instead of overflowing the panel. */
|
its content instead of overflowing the panel. */
|
||||||
.graph-embed {
|
.graph-embed {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
|
|||||||
@@ -96,7 +96,7 @@ const tasksStore = useTasksStore();
|
|||||||
const project = ref<Project | null>(null);
|
const project = ref<Project | null>(null);
|
||||||
|
|
||||||
// Design system the project is styled from. Loaded separately because an
|
// Design system the project is styled from. Loaded separately because an
|
||||||
// install with none is the ordinary case (rule #115) and the picker simply
|
// install with none is the ordinary case and the picker simply
|
||||||
// doesn't render — a failed fetch must not take the project page with it.
|
// doesn't render — a failed fetch must not take the project page with it.
|
||||||
const designSystems = ref<DesignSystem[]>([]);
|
const designSystems = ref<DesignSystem[]>([]);
|
||||||
const editDesignSystemId = ref<number | null>(null);
|
const editDesignSystemId = ref<number | null>(null);
|
||||||
@@ -189,8 +189,8 @@ function groupKey(group: MilestoneGroup): number {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// A group's Done column starts COLLAPSED, to its header and count. Done work is
|
// A group's Done column starts COLLAPSED, to its header and count. Done work is
|
||||||
// the part of a board nobody is reading, and listed in full it pushed the open
|
// the part of a board nobody is reading, and listed in full it pushes the open
|
||||||
// columns of every group below it off the screen (operator, 2026-09-15). Not
|
// columns of every group below it off the screen. Not
|
||||||
// persisted, matching the milestone collapse beside it: each load starts from
|
// persisted, matching the milestone collapse beside it: each load starts from
|
||||||
// the same defaults.
|
// the same defaults.
|
||||||
const expandedDone = ref<Set<number>>(new Set());
|
const expandedDone = ref<Set<number>>(new Set());
|
||||||
@@ -418,7 +418,7 @@ async function loadTasks() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
// Say so. This used to swallow the error and leave an empty board, which is
|
// Say so. A swallowed error leaves an empty board, which is
|
||||||
// indistinguishable from a project with no tasks — the same "hidden with no
|
// indistinguishable from a project with no tasks — the same "hidden with no
|
||||||
// indicator" failure as the truncation above, one layer up.
|
// indicator" failure as the truncation above, one layer up.
|
||||||
tasksError.value = "Could not load tasks. Refresh to try again.";
|
tasksError.value = "Could not load tasks. Refresh to try again.";
|
||||||
@@ -465,7 +465,7 @@ async function loadNotes() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ── Pattern-library coverage (#2692) ─────────────────────────── */
|
/* ── Pattern-library coverage ─────────────────────────────────── */
|
||||||
|
|
||||||
interface CoverageGap {
|
interface CoverageGap {
|
||||||
dir: string;
|
dir: string;
|
||||||
@@ -488,11 +488,11 @@ interface Coverage {
|
|||||||
computed_at: string;
|
computed_at: string;
|
||||||
repos: { repo: string; ref: string; total: number; accounted: number }[];
|
repos: { repo: string; ref: string; total: number; accounted: number }[];
|
||||||
largest_gaps: CoverageGap[];
|
largest_gaps: CoverageGap[];
|
||||||
// The mechanical proposer's standing (#2792): canon proposals awaiting an
|
// The mechanical proposer's standing: canon proposals awaiting an
|
||||||
// agent's confirm, and the biggest repeats-with-no-canon families.
|
// agent's confirm, and the biggest repeats-with-no-canon families.
|
||||||
proposed?: number;
|
proposed?: number;
|
||||||
derive_groups?: DeriveGroup[];
|
derive_groups?: DeriveGroup[];
|
||||||
// The divergence readout (#2793): button B where button A is canon, and
|
// The divergence readout: button B where button A is canon, and
|
||||||
// judged shapes whose bodies moved since they were judged.
|
// judged shapes whose bodies moved since they were judged.
|
||||||
divergent?: number;
|
divergent?: number;
|
||||||
divergence?: Divergence[];
|
divergence?: Divergence[];
|
||||||
@@ -543,7 +543,7 @@ async function refreshCoverage() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ── Forge pin (#2778) — which of the OWNER's connections serves this
|
/* ── Forge pin — which of the OWNER's connections serves this
|
||||||
project. Owner-only UI: the select lists the viewer's own keyring, which
|
project. Owner-only UI: the select lists the viewer's own keyring, which
|
||||||
is only the eligible set when the viewer IS the owner. ── */
|
is only the eligible set when the viewer IS the owner. ── */
|
||||||
|
|
||||||
@@ -737,7 +737,7 @@ async function confirmDelete() {
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Inception (milestone 297): the owner of an undecided project is asked
|
<!-- Inception: the owner of an undecided project is asked
|
||||||
what it inherits; once recorded, one line says what was decided. -->
|
what it inherits; once recorded, one line says what was decided. -->
|
||||||
<InceptionCard
|
<InceptionCard
|
||||||
v-if="project.inception == null && isProjectOwner"
|
v-if="project.inception == null && isProjectOwner"
|
||||||
@@ -779,7 +779,7 @@ async function confirmDelete() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Pattern-library coverage (#2692) — only when a forge is configured;
|
<!-- Pattern-library coverage — only when a forge is configured;
|
||||||
forge-less installs never see this card at all. -->
|
forge-less installs never see this card at all. -->
|
||||||
<div v-if="coverageConfigured" class="coverage-card">
|
<div v-if="coverageConfigured" class="coverage-card">
|
||||||
<div class="coverage-head">
|
<div class="coverage-head">
|
||||||
@@ -874,7 +874,7 @@ async function confirmDelete() {
|
|||||||
the shape ledger and reports how many are classified against canon.
|
the shape ledger and reports how many are classified against canon.
|
||||||
</p>
|
</p>
|
||||||
<p v-if="coverageError" class="coverage-error">{{ coverageError }}</p>
|
<p v-if="coverageError" class="coverage-error">{{ coverageError }}</p>
|
||||||
<!-- Forge pin (#2778): owner-only, because the eligible set is the
|
<!-- Forge pin: owner-only, because the eligible set is the
|
||||||
owner's own keyring. Automatic = resolve by repo host. -->
|
owner's own keyring. Automatic = resolve by repo host. -->
|
||||||
<div v-if="isProjectOwner && forgeOptions.length" class="coverage-forge-row">
|
<div v-if="isProjectOwner && forgeOptions.length" class="coverage-forge-row">
|
||||||
<label for="forge-pin" class="coverage-gaps-label">Forge</label>
|
<label for="forge-pin" class="coverage-gaps-label">Forge</label>
|
||||||
@@ -1200,7 +1200,7 @@ async function confirmDelete() {
|
|||||||
:design-system-id="project.design_system_id ?? null"
|
:design-system-id="project.design_system_id ?? null"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<!-- Family tab (milestone 463): which platforms this project is. -->
|
<!-- Family tab: which platforms this project is. -->
|
||||||
<ProjectFamilyTab
|
<ProjectFamilyTab
|
||||||
v-if="activeTab === 'family'"
|
v-if="activeTab === 'family'"
|
||||||
:project-id="projectId"
|
:project-id="projectId"
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ function toggleLocationFilter() {
|
|||||||
if (!showLocationFilter.value && locationActive.value) clearLocation();
|
if (!showLocationFilter.value && locationActive.value) clearLocation();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Drift check (#2086) — "attention" is everything whose recorded location or
|
// Drift check — "attention" is everything whose recorded location or
|
||||||
// code no longer checks out, plus everything whose verdict expired because the
|
// code no longer checks out, plus everything whose verdict expired because the
|
||||||
// snippet was edited since it was checked.
|
// snippet was edited since it was checked.
|
||||||
const needsAttentionOnly = ref(false);
|
const needsAttentionOnly = ref(false);
|
||||||
@@ -58,7 +58,7 @@ const showMergeModal = ref(false);
|
|||||||
const canonicalId = ref<number | null>(null);
|
const canonicalId = ref<number | null>(null);
|
||||||
const merging = ref(false);
|
const merging = ref(false);
|
||||||
|
|
||||||
// Near-duplicate report (#2088). Loaded on demand, not with the list: it's a
|
// Near-duplicate report. Loaded on demand, not with the list: it's a
|
||||||
// pairwise scan and most visits to this page aren't a tidy-up.
|
// pairwise scan and most visits to this page aren't a tidy-up.
|
||||||
const duplicateGroups = ref<DuplicateGroup[]>([]);
|
const duplicateGroups = ref<DuplicateGroup[]>([]);
|
||||||
const dupLoading = ref(false);
|
const dupLoading = ref(false);
|
||||||
|
|||||||
Reference in New Issue
Block a user