feat: establish workspace web design language

This commit is contained in:
2026-09-17 01:31:54 +09:00
parent c8eb767d63
commit ab6450b9fe
39 changed files with 3626 additions and 614 deletions
+136
View File
@@ -0,0 +1,136 @@
// @ts-nocheck
function assert(condition: unknown, message: string): asserts condition {
if (!condition) throw new Error(message);
}
Deno.test("Bevel keeps depth visual while semantic controls remain children", async () => {
const source = await Deno.readTextFile(
new URL("../src/lib/workspace/ui/Bevel.svelte", import.meta.url),
);
const appCss = await Deno.readTextFile(
new URL("../src/app.css", import.meta.url),
);
const designLanguage = await Deno.readTextFile(
new URL(
"../../../docs/development/ui-ux/design-language.md",
import.meta.url,
),
);
for (
const contract of [
"export type BevelProfile = 'edge' | 'ridge'",
"export type BevelDepth = 'raised' | 'inset'",
"children: Snippet<[]>",
"top?: boolean",
"right?: boolean",
"bottom?: boolean",
"left?: boolean",
"pointer-events: none",
"data-pressed={pressed || undefined}",
"aria-invalid='true'",
"forced-colors: active",
"mask-composite: exclude",
]
) {
assert(source.includes(contract), `Bevel must preserve ${contract}`);
}
assert(
source.includes("@supports") &&
source.includes("border-style: solid") &&
source.includes(
"border-width: var(--_top-w) var(--_right-w) var(--_bottom-w) var(--_left-w)",
),
"Bevel must retain a side-selective plain border fallback around the enhanced edge",
);
assert(
source.includes("calc(max(cos(180deg - var(--_L)), 0) * 100%)") &&
source.includes("var(--_s6) 90deg") &&
source.includes("var(--_s6) 270deg") &&
!source.includes("+ 1) * 50%"),
"Bevel must clamp unlit faces to the configured shadow without ambient light",
);
assert(
appCss.includes("--bevel-face-width: 2px") &&
source.includes("--_W: var(--bevel-face-width)") &&
source.includes("inset: var(--bevel-face-width)") &&
!source.includes("BevelSize") &&
!source.includes("data-size"),
"Bevel must use the project-wide 2px edge and compose ridge from two 2px faces",
);
assert(
source.includes("data-top={top ? 'true' : 'false'}") &&
source.includes("data-right={right ? 'true' : 'false'}") &&
source.includes("data-bottom={bottom ? 'true' : 'false'}") &&
source.includes("data-left={left ? 'true' : 'false'}") &&
source.includes(".bevel[data-top='false'] {") &&
source.includes("--bevel-top-left-radius: 0px") &&
source.includes("--bevel-top-right-radius: 0px") &&
source.includes(".bevel[data-right='false'] {") &&
source.includes("--bevel-bottom-right-radius: 0px") &&
source.includes(".bevel[data-bottom='false'] {") &&
source.includes("--bevel-bottom-left-radius: 0px") &&
source.includes(".bevel[data-left='false'] {") &&
source.includes("--bevel-top-inset: 0px") &&
source.includes("--bevel-right-inset: 0px") &&
source.includes("--bevel-bottom-inset: 0px") &&
source.includes("--bevel-left-inset: 0px"),
"Bevel must draw selected sides and round only corners shared by two enabled sides",
);
assert(
source.includes("data-profile={profile}") &&
source.includes("[data-profile='ridge'][data-depth='raised']::after") &&
source.includes("[data-profile='ridge'][data-depth='inset']::after") &&
!source.includes("data-depth='ridge'"),
"Bevel must keep edge/ridge profile orthogonal to raised/inset depth",
);
assert(
appCss.includes("--bevel-highlight: #fff") &&
appCss.includes("--bevel-shadow: #000") &&
!source.includes("surface") &&
!source.includes("tone") &&
!source.includes("background-color:"),
"Bevel must use the shared highlight and shadow palette without owning a surface tone",
);
assert(
designLanguage.includes("Bevelはedge") &&
designLanguage.includes("lightingだけを所有する") &&
designLanguage.includes("text/content areaだけとする"),
"The design language must prohibit Bevel surface colors outside the child content area",
);
assert(
source.includes("button, input, select, textarea"),
"Bevel must leave native semantic controls inside the visual wrapper",
);
});
Deno.test("Workspace showroom exercises raised inset and ridge edges", async () => {
const workspaceSource = await Deno.readTextFile(
new URL(
"../src/routes/design-lab/workspace-web-ux/+page.svelte",
import.meta.url,
),
);
const settingsSource = await Deno.readTextFile(
new URL(
"../src/routes/design-lab/workspace-web-ux/settings/+page.svelte",
import.meta.url,
),
);
assert(
workspaceSource.includes('profile="edge" depth="raised"') &&
workspaceSource.includes('profile="ridge" depth="raised"') &&
workspaceSource.includes('profile="ridge" depth="inset"') &&
workspaceSource.includes("right={false} bottom={false}") &&
workspaceSource.includes("top={false} left={false}") &&
workspaceSource.includes("pressed"),
"Workspace showroom must demonstrate edge and ridge depths with full and adjacent side selections",
);
assert(
settingsSource.includes('profile="edge" depth="inset"') &&
settingsSource.includes('aria-invalid="true"'),
"Settings showroom must demonstrate inset and invalid fields",
);
});
@@ -0,0 +1,112 @@
// @ts-nocheck
function assert(condition: unknown, message: string): asserts condition {
if (!condition) throw new Error(message);
}
Deno.test("BevelLine exposes a semantic directional separator", async () => {
const source = await Deno.readTextFile(
new URL("../src/lib/workspace/ui/BevelLine.svelte", import.meta.url),
);
const appCss = await Deno.readTextFile(
new URL("../src/app.css", import.meta.url),
);
for (
const contract of [
"export type BevelLineDirection = 'x' | 'y'",
"length?: string",
"as?: 'span' | 'div'",
"role={decorative ? undefined : 'separator'}",
"'horizontal' : 'vertical'",
"style:width={direction === 'x' ? length : undefined}",
"style:height={direction === 'y' ? length : undefined}",
"pointer-events: none",
"forced-colors: active",
]
) {
assert(source.includes(contract), `BevelLine must preserve ${contract}`);
}
assert(
appCss.includes("--bevel-face-width: 2px") &&
source.includes("--bevel-line-face-width: var(--bevel-face-width)") &&
source.includes(
"--bevel-line-width: calc(var(--bevel-line-face-width) * 2)",
) &&
!source.includes("BevelLineWeight") &&
!source.includes("data-weight"),
"BevelLine must compose a fixed 4px ridge from two project-wide 2px faces",
);
assert(
source.includes("background: var(--line-strong)") &&
source.includes("@supports") &&
source.includes("linear-gradient(to bottom") &&
source.includes("linear-gradient(to right"),
"BevelLine must retain a plain fallback and directional enhanced edges",
);
assert(
source.includes("var(--bevel-highlight)") &&
source.includes("var(--bevel-shadow)") &&
!source.includes("surface"),
"BevelLine must use the shared highlight and shadow palette without a surface color",
);
assert(
source.includes("calc(max(cos(180deg - var(--_L)), 0) * 100%)") &&
source.includes("var(--_s6) 90deg") &&
source.includes("var(--_s6) 270deg") &&
!source.includes("+ 1) * 50%"),
"BevelLine must clamp unlit faces to the configured shadow without ambient light",
);
});
Deno.test("Workspace showroom demonstrates both BevelLine directions", async () => {
const source = await Deno.readTextFile(
new URL(
"../src/routes/design-lab/workspace-web-ux/+page.svelte",
import.meta.url,
),
);
assert(
source.includes('direction="x" length="100%"') &&
source.includes('direction="y" length="72px"') &&
source.includes('direction="x" length="160px" depth="inset"') &&
!source.includes("weight="),
"Workspace showroom must demonstrate horizontal, vertical, and inset BevelLine states",
);
});
Deno.test("Workspace shell and showroom use BevelLine for structural separators", async () => {
const rootLayout = await Deno.readTextFile(
new URL("../src/routes/+layout.svelte", import.meta.url),
);
const sidebarFrame = await Deno.readTextFile(
new URL(
"../src/lib/workspace/sidebar/SidebarFrame.svelte",
import.meta.url,
),
);
const sidebarCss = await Deno.readTextFile(
new URL("../src/lib/workspace/sidebar/sidebar.css", import.meta.url),
);
const showroomCss = await Deno.readTextFile(
new URL(
"../src/routes/design-lab/workspace-web-ux/showroom.css",
import.meta.url,
),
);
assert(
rootLayout.includes("app-shell__topbar-divider") &&
sidebarFrame.includes("sidebar-frame__divider--vertical") &&
sidebarFrame.includes("sidebar-frame__divider--horizontal"),
"Workspace shell must render structural dividers through BevelLine",
);
assert(
!sidebarCss.includes("border-right:") &&
!sidebarCss.includes("border-bottom:") &&
!showroomCss.includes("border-top: 1px solid") &&
!showroomCss.includes("border-bottom: 1px solid"),
"Shell and showroom structural separators must not fall back to one-sided solid borders",
);
});
@@ -0,0 +1,59 @@
// @ts-nocheck
function assert(condition: unknown, message: string): asserts condition {
if (!condition) throw new Error(message);
}
Deno.test("Tooltip exposes the same contextual help to pointer and keyboard users", async () => {
const source = await Deno.readTextFile(
new URL("../src/lib/workspace/ui/Tooltip.svelte", import.meta.url),
);
for (
const contract of [
'role="tooltip"',
"onpointerenter={showAfterDelay}",
"onfocusin={showImmediately}",
"event.key === 'Escape'",
"children: Snippet<[descriptionId: string]>",
"max-width: min(20rem, calc(100vw - var(--space-4)))",
]
) {
assert(source.includes(contract), `Tooltip must preserve ${contract}`);
}
});
Deno.test("Tooltip showroom binds help to actions and column headings", async () => {
const workspaceSource = await Deno.readTextFile(
new URL(
"../src/routes/design-lab/workspace-web-ux/+page.svelte",
import.meta.url,
),
);
const settingsSource = await Deno.readTextFile(
new URL(
"../src/routes/design-lab/workspace-web-ux/settings/+page.svelte",
import.meta.url,
),
);
for (const descriptionId of ["rerun-help", "last-seen-help", "source-help"]) {
assert(
workspaceSource.includes(descriptionId),
`Workspace showroom must expose contextual help ${descriptionId}`,
);
}
assert(
settingsSource.includes("retry-runtime-help"),
"Settings showroom must expose retry operation help",
);
assert(
workspaceSource.includes('class="column-help"') &&
workspaceSource.includes("aria-describedby={descriptionId}>Last seen"),
"Column help must be bound to the column label rather than a separate info button",
);
assert(
!workspaceSource.includes("Actions explain") &&
!workspaceSource.includes("Use a table when"),
"Showroom must not restore persistent operation or table explanation",
);
});