fix(design-systems): the empty state's create button did nothing, and one surface
CI & Build / Python lint (push) Successful in 4s
CI & Build / Plugin hooks (push) Successful in 12s
CI & Build / integration (push) Successful in 28s
CI & Build / TypeScript typecheck (push) Successful in 33s
CI & Build / Python tests (push) Successful in 57s
CI & Build / Build & push image (push) Successful in 47s
CI & Build / Python lint (push) Successful in 4s
CI & Build / Plugin hooks (push) Successful in 12s
CI & Build / integration (push) Successful in 28s
CI & Build / TypeScript typecheck (push) Successful in 33s
CI & Build / Python tests (push) Successful in 57s
CI & Build / Build & push image (push) Successful in 47s
Two reports, one root cause each. **The dead button.** "Create the first one" set `showCreate = true`, but the create form lived inside `<div v-else class="ds-body">` — the sibling branch of the empty state. The two are mutually exclusive, so on a fresh install the flag flipped and nothing rendered. The first action a new install can take was the one that didn't work, which is a poor way to honour "an install with zero design systems is the ordinary state". The first system now gets its own form outside the list layout, and it drops the parent picker entirely: there is nothing to inherit from yet, so it says so instead of offering an empty select. **Two surfaces, the wrong one first.** /design and /design-systems are halves of one thing — the record that decides the styling, and what the browser renders from it — and I had added them as two separate nav entries with the read-only diagnostic listed first. Backwards: the record is what you work with; the live view is the check on it. Now one nav entry pointing at the record, with a shared tab bar joining the two. The explorer is renamed "Live tokens", which is what it actually shows. The tab bar is a component rather than the same markup in both views. Two copies diverge the moment a third tab appears — and a design surface that ships duplicated markup would be arguing against itself.
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
@@ -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">
|
||||
|
||||
@@ -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 → app inheritance — see
|
||||
<router-link to="/design-systems">design systems</router-link>.
|
||||
</p>
|
||||
</header>
|
||||
|
||||
<!-- Drift: what the rulebook claims vs what the tokens do. -->
|
||||
|
||||
Reference in New Issue
Block a user