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
190 changes: 190 additions & 0 deletions docs/redesign/HANDOFF.md

Large diffs are not rendered by default.

1,221 changes: 1,221 additions & 0 deletions docs/redesign/prototype/Prototype.dc.html

Large diffs are not rendered by default.

125 changes: 125 additions & 0 deletions docs/redesign/tokens.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
/*
* Parallax Maker redesign — design tokens.
* Replaces the :root / :root.dark blocks in frontend/src/app.css.
* Dark is the default. Set data-theme="light" on <html> for light mode.
* Fonts: IBM Plex Sans (400/500/600) and IBM Plex Mono (400/500).
*/

:root,
[data-theme='dark'] {
color-scheme: dark;

/* Surfaces */
--color-bg: #111214; /* app background, canvas surround */
--color-surface: #18191c; /* panels: header, layers, inspector, status bar */
--color-surface-raised: #1f2124;/* inputs, cards, segmented-control track */
--color-surface-hover: #27292e; /* hover rows, secondary buttons */
--color-float: #1c1d21; /* floating toolbars, toasts, dialogs */
--color-canvas: #111214;
--color-checker-1: #26282c;
--color-checker-2: #2f3136;
--color-scrim: rgb(0 0 0 / 55%);

/* Lines */
--color-border: #2a2d32;
--color-border-strong: #383b41;

/* Text */
--color-text: #e8e9ec;
--color-text-secondary: #b0b4bb;
--color-text-muted: #8d929b; /* 5.5:1 on --color-surface */

/* Primary action (one per context) */
--color-primary: #f0a13a;
--color-primary-text: #1b1206;
--color-primary-soft: #2b2419; /* current step background */
--color-primary-soft-text: #f5b865;

/* Selection (layer row, candidate, active tool, SAM mask) */
--color-selection: #4c9dff;
--color-selection-text: #cfe3ff;
--color-selection-soft: #1e2a3b;
--color-selection-tint: rgb(76 157 255 / 50%);

/* Status */
--color-success: #8fd1a8; /* done steps, ground badge */
--color-success-soft: #1f3a2a;
--color-success-line: #2f5a41;
--color-danger: #ff8a80;
--color-danger-soft: #3a1d1f;
--color-danger-line: #6b2c2c;

/* Canvas overlays */
--color-horizon: #ff8a3d;
--color-inpaint-mask: rgb(255 96 64 / 50%);
--color-point-positive: #2f7de1;
--color-point-negative: #e8742a;
}

[data-theme='light'] {
color-scheme: light;
--color-bg: #eceef1;
--color-surface: #f8f9fa;
--color-surface-raised: #ffffff;
--color-surface-hover: #eceef1;
--color-float: #ffffff;
--color-canvas: #e1e3e7;
--color-checker-1: #f4f5f7;
--color-checker-2: #e3e5e9;
--color-scrim: rgb(20 22 26 / 35%);
--color-border: #dcdfe4;
--color-border-strong: #c9cdd3;
--color-text: #1b1d21;
--color-text-secondary: #474c54;
--color-text-muted: #5d636d;
--color-primary: #e8922a;
--color-primary-text: #1b1206;
--color-primary-soft: #fdf0dd;
--color-primary-soft-text: #8a4f00;
--color-selection: #2f7de1;
--color-selection-text: #174a8c;
--color-selection-soft: #e2edfd;
--color-selection-tint: rgb(47 125 225 / 45%);
--color-success: #1c7240;
--color-success-soft: #e3f5ea;
--color-success-line: #9fd3b3;
--color-danger: #b42727;
--color-danger-soft: #fdeaea;
--color-danger-line: #f0b4b4;
--color-horizon: #d9601a;
--color-inpaint-mask: rgb(230 80 50 / 45%);
--color-point-positive: #2f7de1;
--color-point-negative: #d9601a;
}

:root {
--font-sans: 'IBM Plex Sans', system-ui, -apple-system, 'Segoe UI', sans-serif;
--font-mono: 'IBM Plex Mono', ui-monospace, 'SF Mono', Menlo, monospace;
--text-base: 13px;
--text-small: 11.5px;
--text-label: 11px; /* uppercase, 600, letter-spacing .06em */
--text-title: 16px;

--space-1: 4px;
--space-2: 8px;
--space-3: 12px;
--space-4: 16px;
--space-6: 24px;

--radius-sm: 4px; /* chips, kbd, thumbnails */
--radius-md: 6px; /* buttons, inputs, rows */
--radius-lg: 8px; /* floating toolbars, cards, candidates */
--radius-xl: 10px; /* dialogs */

--control-h: 32px;
--control-h-sm: 26px;
--shadow-float: 0 8px 24px rgb(0 0 0 / 30%);

/* Layout */
--header-h: 48px;
--statusbar-h: 28px;
--viewbar-h: 40px;
--toolopts-h: 44px;
--layers-w: 300px;
--inspector-w: 328px;
}
64 changes: 31 additions & 33 deletions e2e/drivers/svelte.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,14 @@ import type {
* implemented (see docs/svelte-migration/PARITY.md); `supports()` always
* returns `true`.
*/
/** The workflow step whose Inspector panel is each former main tab. */
const STEP_FOR_TAB: Record<Exclude<MainTab, 'Configuration'>, string> = {
Mode: 'depth',
Segmentation: 'slices',
Inpainting: 'inpaint',
Export: 'export',
};

export class SvelteDriver implements UiDriver {
readonly target: UiTarget = 'svelte';

Expand All @@ -39,11 +47,20 @@ export class SvelteDriver implements UiDriver {
await expect(this.page.getByRole('heading', { name: 'Parallax Maker' })).toBeVisible();
}

/**
* The redesigned UI has no workflow tabs: each former tab is a workflow
* step's Inspector panel, and Configuration is the Settings panel.
*/
async openTab(tab: MainTab): Promise<void> {
const button = this.page.getByRole('tab', { name: tab, exact: true });
await expect(button).toHaveCount(1);
await button.click();
await expect(button).toHaveAttribute('aria-selected', 'true');
const inspector = this.page.getByTestId('inspector');
if (tab === 'Configuration') {
if ((await inspector.getAttribute('data-panel')) !== 'Configuration') {
await this.page.getByTestId('open-settings').click();
}
} else {
await this.page.getByTestId(`step-${STEP_FOR_TAB[tab]}`).click();
}
await expect(inspector).toHaveAttribute('data-panel', tab);
}

// Observable elements
Expand Down Expand Up @@ -136,43 +153,24 @@ export class SvelteDriver implements UiDriver {
}

private async activeMainTab(): Promise<MainTab | null> {
// Scoped to the workflow tablist specifically: the viewer (2D/3D) tabs
// also use role="tab" and are also selected by default, so a
// page-wide `getByRole('tab', { selected: true })` would be ambiguous.
const active = this.page.locator('[role="tablist"][aria-label="Workflow"] [role="tab"][aria-selected="true"]');
const count = await active.count();
if (count === 0) return null;
const name = await active.first().textContent();
const trimmed = name?.trim();
const panel = await this.page.getByTestId('inspector').getAttribute('data-panel');
const known: MainTab[] = ['Mode', 'Segmentation', 'Inpainting', 'Export', 'Configuration'];
return (known.find((tab) => tab === trimmed) as MainTab | undefined) ?? null;
return (known.find((tab) => tab === panel) as MainTab | undefined) ?? null;
}

/**
* The Mode Selector lives in the Svelte-only "Mode" workflow tab (not
* part of the shared `MainTab` type -- Dash keeps its Mode Selector
* inline, outside any tab). Runs `fn` with that tab visible, using real
* clicks (no forced actions on a hidden `<select>`), then restores
* whichever workflow tab was active before.
* The Mode Selector lives in the Depth step's Inspector panel (Dash kept
* it inline, outside any tab). Runs `fn` with that panel visible, using
* real clicks (no forced actions on a hidden `<select>`), then restores
* whichever panel was showing before.
*/
private async withModeTabVisible<T>(fn: () => Promise<T>): Promise<T> {
const tablist = '[role="tablist"][aria-label="Workflow"] [role="tab"]';
const active = this.page.locator(`${tablist}[aria-selected="true"]`);
const previousTab = (await active.count()) > 0 ? (await active.first().textContent())?.trim() : null;

if (previousTab !== 'Mode') {
const modeButton = this.page.getByRole('tab', { name: 'Mode', exact: true });
await modeButton.click();
await expect(modeButton).toHaveAttribute('aria-selected', 'true');
}
const previousTab = await this.activeMainTab();
if (previousTab !== 'Mode') await this.openTab('Mode');
try {
return await fn();
} finally {
if (previousTab && previousTab !== 'Mode') {
const button = this.page.getByRole('tab', { name: previousTab, exact: true });
await button.click();
await expect(button).toHaveAttribute('aria-selected', 'true');
}
if (previousTab && previousTab !== 'Mode') await this.openTab(previousTab);
}
}

Expand Down Expand Up @@ -467,7 +465,7 @@ export class SvelteDriver implements UiDriver {
// Project / configuration

async expectDarkTheme(): Promise<void> {
await expect(this.page.locator('#app-container')).toHaveClass(/\bdark\b/);
await expect(this.page.locator('html')).toHaveAttribute('data-theme', 'dark');
}

async expectSliderValue(name: SliderName, value: number): Promise<void> {
Expand Down
7 changes: 6 additions & 1 deletion e2e/helpers/image.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,13 @@ export type RGB = readonly [number, number, number];
export type RGBA = readonly [number, number, number, number];
export type ImageMetadata = { width: number; height: number; hash: string };

/**
* Waits until the image element has a fully decoded image. It need not be
* on screen: a UI may keep it in a view or panel that isn't showing (the
* redesigned UI shows the depth map only in its Depth view).
*/
export async function waitForImage(image: Locator): Promise<void> {
await expect(image).toBeVisible();
await expect(image).toBeAttached();
await expect
.poll(() =>
image.evaluate((element: HTMLImageElement) => ({
Expand Down
Loading
Loading