Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 73 additions & 2 deletions configurator/src/components/inputs/TokenRow.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,33 @@
import type { SlashedToken } from '../../types';
import { resolveColor, previewVersion } from '../../lib/previewResolver.svelte';
import { scaleForValue } from '../../lib/variableScales';
import { roleOf, aliasTargetOf, tokenState, ROLE_LABEL, type TokenRole, type TokenState } from '../../lib/tokenModel';

let { token, overrideValue, onSet, onReset }: {
let { token, overrideValue, onSet, onReset, dependentsCount = 0 }: {
token: SlashedToken;
overrideValue?: string;
onSet: (value: string) => void;
onReset: () => void;
/** How many other tokens reference this one (from the dependency graph). */
dependentsCount?: number;
} = $props();

// --- Token model: role, alias target and override state -------------------
// These make the row honest about *what kind of token* is being edited and
// whether the current override quietly disconnects it from the system that
// produces it (a generated output/alias/scale step). See lib/tokenModel.ts.
let role = $derived<TokenRole>(roleOf(token));
let aliasTarget = $derived(aliasTargetOf(token));
let overrideState = $derived<TokenState>(
overrideValue === undefined ? "default" : tokenState(token, { [token.name]: overrideValue })
);
const ROLE_STYLE: Record<TokenRole, string> = {
source: "text-slate-500 dark:text-slate-400 bg-black/5 dark:bg-white/8",
alias: "text-sky-700 dark:text-sky-300 bg-sky-500/10",
output: "text-violet-700 dark:text-violet-300 bg-violet-500/10",
};
let aliasShort = $derived(aliasTarget ? aliasTarget.replace("--sf-", "") : "");

const CUSTOM = "__sf_custom__";
let expanded = $state(false);

Expand Down Expand Up @@ -73,9 +92,22 @@
style:background={swatchColor}
></div>
{/if}
<div class="text-[10px] font-mono text-slate-700 dark:text-slate-300 truncate flex-1" title={token.name}>
<div class="text-[10px] font-mono text-slate-700 dark:text-slate-300 truncate min-w-0 flex-1" title={token.name}>
{shortName}
</div>
<!-- Role badge: source (settable) · alias (re-export) · output (generated).
Makes it obvious when you're about to edit something the system derives
rather than a knob you own. -->
<span
class={`shrink-0 px-1 py-px rounded text-[7px] font-bold uppercase tracking-wider ${ROLE_STYLE[role]}`}
title={role === "source"
? "Source — a value you set"
: role === "alias"
? `Alias — re-exports ${aliasShort}`
: "Output — generated from other tokens"}
>
{ROLE_LABEL[role]}
</span>
{#if isOverridden}
<button
onclick={onReset}
Expand All @@ -91,6 +123,45 @@
<div class="text-[9px] text-slate-400 dark:text-slate-600 leading-snug mt-0.5 pl-3.5">{token.description}</div>
{/if}

<!-- Relationship context: where this token inherits from, and what depends on
it. Turns the flat list into a navigable graph the audit found missing. -->
{#if aliasTarget || dependentsCount > 0}
<div class="flex flex-wrap items-center gap-x-2 gap-y-0.5 mt-0.5 pl-3.5 text-[8px] text-slate-400 dark:text-slate-600">
{#if aliasTarget}
<span class="font-mono">↳ inherits <span class="text-sky-600 dark:text-sky-400">{aliasShort}</span></span>
{/if}
{#if dependentsCount > 0}
<span title={`${dependentsCount} token${dependentsCount !== 1 ? "s" : ""} read this value`}>
used by {dependentsCount}
</span>
{/if}
</div>
{/if}

<!-- Detached / invalid warning: only when the override actually disconnects
the token from its generator, or fails validation. -->
{#if overrideState === "detached"}
<div class="flex items-start gap-1 mt-1 ml-3.5 px-1.5 py-1 rounded bg-amber-500/10 text-[8px] text-amber-700 dark:text-amber-300 leading-snug">
<span class="font-bold shrink-0">Detached</span>
<span>
{role === "alias"
? `frozen — no longer follows ${aliasShort}.`
: role === "output"
? "frozen — no longer derived from its source tokens."
: "a generated scale step is pinned; its source knob won't move it."}
<button onclick={onReset} class="underline hover:text-amber-900 dark:hover:text-amber-100 cursor-pointer">Restore link</button>
</span>
</div>
{:else if overrideState === "invalid"}
<div class="flex items-start gap-1 mt-1 ml-3.5 px-1.5 py-1 rounded bg-rose-500/10 text-[8px] text-rose-700 dark:text-rose-300 leading-snug">
<span class="font-bold shrink-0">Invalid</span>
<span>
this value can't be applied safely.
<button onclick={onReset} class="underline hover:text-rose-900 dark:hover:text-rose-100 cursor-pointer">Reset</button>
</span>
</div>
{/if}

<div class="mt-1 pl-3.5">
{#if showScalePicker && scaleOpts}
<select
Expand Down
6 changes: 6 additions & 0 deletions configurator/src/components/panels/AllTokensTab.svelte
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<script lang="ts">
import type { SlashedToken } from '../../types';
import TokenRow from '../inputs/TokenRow.svelte';
import { buildDependencyGraph } from '../../lib/tokenModel';

let { tokens, overrides, patterns, onSet, onReset }: {
tokens: SlashedToken[];
Expand All @@ -12,6 +13,10 @@

const TIER_ORDER: Record<string, number> = { PUBLIC: 0, "PUBLIC-ADVANCED": 1, INTERNAL: 2 };

// Whole-catalogue dependency graph (built once from the full token set, not
// just this domain's slice) so every row can show how many tokens read it.
let graph = $derived(buildDependencyGraph(tokens));

let query = $state("");
let showInternal = $state(false);
let onlyModified = $state(false);
Expand Down Expand Up @@ -141,6 +146,7 @@
<TokenRow
token={t}
overrideValue={overrides[t.name]}
dependentsCount={graph.usedBy[t.name]?.length ?? 0}
onSet={(v) => onSet(t.name, v)}
onReset={() => onReset(t.name)}
/>
Expand Down
Loading