Fix the dead create button on a fresh install, and make Design one surface (#89)
CI & Build / Python lint (push) Successful in 4s
CI & Build / Plugin hooks (push) Successful in 10s
CI & Build / integration (push) Successful in 30s
CI & Build / TypeScript typecheck (push) Successful in 35s
CI & Build / Python tests (push) Successful in 56s
CI & Build / Build & push image (push) Successful in 22s

This commit was merged in pull request #89.
This commit is contained in:
2026-07-31 08:32:07 -04:00
4 changed files with 99 additions and 14 deletions
+7 -5
View File
@@ -64,10 +64,13 @@ router.afterEach(() => {
<Moon v-else :size="16" />
</button>
<!-- Design explorer. An icon rather than a sixth primary nav link: it's a
<!-- Design. An icon rather than a sixth primary nav link: it's a
meta-surface like Trash and Settings, but hiding it entirely would
defeat the point of having somewhere the design system is visible. -->
<router-link to="/design" class="btn-icon" aria-label="Design" title="Design">
defeat the point of having somewhere the design system is visible.
Points at the RECORD, not the live-token view — the record is what
you work with; the live view is the check on it, and it's a tab
away. -->
<router-link to="/design-systems" class="btn-icon" aria-label="Design" title="Design">
<Palette :size="16" />
</router-link>
@@ -105,8 +108,7 @@ router.afterEach(() => {
<router-link to="/rules" class="nav-link">Rulebooks</router-link>
<router-link to="/shared" class="nav-link">Shared</router-link>
<div class="mobile-divider"></div>
<router-link to="/design" class="nav-link">Design</router-link>
<router-link to="/design-systems" class="nav-link">Design systems</router-link>
<router-link to="/design-systems" class="nav-link">Design</router-link>
<router-link to="/trash" class="nav-link">Trash</router-link>
<router-link to="/settings" class="nav-link">Settings</router-link>
<div class="mobile-divider"></div>
+51
View File
@@ -0,0 +1,51 @@
<script setup lang="ts">
/**
* Sub-navigation for the Design surface.
*
* There are two pages here and they are halves of ONE thing: the record that
* decides the styling, and what the browser is actually rendering from it. They
* were briefly two top-level nav entries, which put the read-only diagnostic
* first and buried the editable record under it — backwards, since the record
* is the thing you work with and the live view is the check on it.
*
* A component rather than the same markup pasted into both views: two copies of
* a tab bar diverge the moment a third tab appears, and that is the exact shape
* of duplication this whole surface exists to make visible.
*/
</script>
<template>
<nav class="design-tabs" aria-label="Design views">
<router-link to="/design-systems" class="design-tab">Design system</router-link>
<router-link to="/design" class="design-tab">Live tokens</router-link>
</nav>
</template>
<style scoped>
.design-tabs {
display: flex;
gap: 0.25rem;
margin-bottom: 1.25rem;
border-bottom: 1px solid var(--color-border);
}
.design-tab {
padding: 0.5rem 0.9rem;
font-size: 0.9rem;
color: var(--color-text-secondary);
text-decoration: none;
border-bottom: 2px solid transparent;
margin-bottom: -1px;
}
.design-tab:hover {
color: var(--color-text);
}
/* `router-link-active` rather than `-exact-active`: both routes are leaves, and
exact matching would drop the highlight on any future child route. */
.design-tab.router-link-active {
color: var(--color-primary);
border-bottom-color: var(--color-primary);
}
</style>
+37 -3
View File
@@ -42,6 +42,7 @@ import {
type StylesheetResult,
} from "@/api/designSystems";
import { listRulebooks, type Rulebook } from "@/api/rulebooks";
import DesignTabs from "@/components/DesignTabs.vue";
import { ApiError } from "@/api/client";
import { useToastStore } from "@/stores/toast";
@@ -528,14 +529,14 @@ function isColourish(value: string): boolean {
<template>
<div class="ds-view">
<DesignTabs />
<header class="ds-header">
<h1>Design systems</h1>
<p class="lede">
The design system as the record holds it. A system with a parent stores
only what it changes, so its own list is the answer to "what does this
one alter?" nothing to compute.
<router-link to="/design">The design page</router-link> shows the other
side: what the browser is actually rendering right now.
</p>
</header>
@@ -547,7 +548,7 @@ function isColourish(value: string): boolean {
</div>
<!-- Empty is the ordinary state for most installs, not a broken one. -->
<div v-else-if="!systems.length" class="notice">
<div v-else-if="!systems.length && !showCreate" class="notice">
<strong>No design systems yet.</strong>
<p>
A design system holds your tokens — colours, spacing, radii — as records
@@ -557,6 +558,39 @@ function isColourish(value: string): boolean {
<button class="btn-primary" @click="showCreate = true">Create the first one</button>
</div>
<!-- The FIRST system gets its own form, outside the list layout below.
`.ds-body` and the empty state are mutually exclusive branches, so a
form nested in the former is unreachable from the latter — which is
exactly the dead button this replaces. Once a system exists, the form
inside `.ds-body` takes over and this branch never renders. -->
<section v-else-if="!systems.length" class="ds-section">
<h2>New design system</h2>
<p class="section-note">
Nothing to inherit from yet, so this one is a family system — the house
style everything else departs from.
</p>
<div class="field">
<label class="field-label" for="first-title">Title</label>
<input
id="first-title" v-model="newTitle" class="input" type="text"
placeholder="FabledSword" @keyup.enter="submitCreate"
/>
</div>
<div class="field">
<label class="field-label" for="first-desc">Description</label>
<input
id="first-desc" v-model="newDescription" class="input" type="text"
placeholder="What it covers"
/>
</div>
<div class="row-actions">
<button class="btn-primary" :disabled="!newTitle.trim() || creating" @click="submitCreate">
{{ creating ? "Creating…" : "Create" }}
</button>
<button class="btn-ghost" @click="showCreate = false">Cancel</button>
</div>
</section>
<div v-else class="ds-body">
<!-- Systems list -->
<aside class="ds-sidebar">
+4 -6
View File
@@ -21,6 +21,7 @@
import { computed, onMounted, ref } from "vue";
import { fetchDesignExpectations } from "@/api/design";
import DesignTabs from "@/components/DesignTabs.vue";
import PriorityBadge from "@/components/PriorityBadge.vue";
import StatusBadge from "@/components/StatusBadge.vue";
import TagPill from "@/components/TagPill.vue";
@@ -101,8 +102,10 @@ const TYPE_SPECIMENS = [
<template>
<div class="design-view">
<DesignTabs />
<header class="design-header">
<h1>Design</h1>
<h1>Live tokens</h1>
<p class="lede">
The system as it actually is. Token values are read from the browser at
runtime, so this page reflects the live cascade rather than what the
@@ -110,11 +113,6 @@ const TYPE_SPECIMENS = [
the system has no shared implementation, it is marked missing rather
than mocked up.
</p>
<p class="lede">
For the other half the design system as a record you can edit, with
family&nbsp;&rarr;&nbsp;app inheritance see
<router-link to="/design-systems">design systems</router-link>.
</p>
</header>
<!-- Drift: what the rulebook claims vs what the tokens do. -->