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
159 changes: 159 additions & 0 deletions docs/superpowers/plans/2026-07-28-image-template-tag-chip-polish.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
# Image Template Tag Chip Polish Implementation Plan

> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.

**Goal:** Polish the image-template tag chips with the approved compact typography and hide the native horizontal scrollbar without removing horizontal scrolling.

**Architecture:** Keep the existing React markup and filtering behavior unchanged. Add one focused CSS contract test to the existing dialogue screen suite, then make the minimum stylesheet-only change in `dialogue.css`.

**Tech Stack:** React, TypeScript, CSS, Vitest, Testing Library, Vite.

---

### Task 1: Lock the approved chip typography and overflow behavior

**Files:**
- Modify: `src/renderer/screens/DialogueScreens.test.tsx`
- Modify: `src/renderer/styles/dialogue.css:973-1006`

- [ ] **Step 1: Write the failing stylesheet contract test**

Add this test beside the existing image-template tag filtering tests in `src/renderer/screens/DialogueScreens.test.tsx`:

```tsx
it("uses compact tag chips without a visible native horizontal scrollbar", () => {
const css = readFileSync("src/renderer/styles/dialogue.css", "utf8");
const rowRule = css.match(/\.image-template-tag-filters\s*\{[^}]*\}/s)?.[0] ?? "";
const chipRule = css.match(/\.image-template-tag-filters button\s*\{[^}]*\}/s)?.[0] ?? "";
const countRule = css.match(/\.image-template-tag-filters button b\s*\{[^}]*\}/s)?.[0] ?? "";
const interactiveCountRule = css.match(/\.image-template-tag-filters button:hover b,\s*\.image-template-tag-filters button\.is-selected b\s*\{[^}]*\}/s)?.[0] ?? "";
const webkitScrollbarRule = css.match(/\.image-template-tag-filters::\-webkit-scrollbar\s*\{[^}]*\}/s)?.[0] ?? "";

expect(rowRule).toContain("overflow-x: auto;");
expect(rowRule).toContain("scrollbar-width: none;");
expect(rowRule).toContain("-webkit-overflow-scrolling: touch;");
expect(chipRule).toContain("min-height: 26px;");
expect(chipRule).toContain("gap: 4px;");
expect(chipRule).toContain("padding: 2px 8px;");
expect(chipRule).toContain("font-size: 12px;");
expect(chipRule).toContain("font-weight: 500;");
expect(chipRule).toContain("line-height: 1.2;");
expect(countRule).toContain("color: var(--n-muted);");
expect(countRule).toContain("font-size: 10px;");
expect(countRule).toContain("font-weight: 500;");
expect(interactiveCountRule).toContain("color: inherit;");
expect(webkitScrollbarRule).toContain("display: none;");
});
```

- [ ] **Step 2: Run the focused test and verify RED**

Run:

```bash
npm test -- --run src/renderer/screens/DialogueScreens.test.tsx -t "uses compact tag chips without a visible native horizontal scrollbar"
```

Expected: FAIL because the current row uses `scrollbar-width: thin`, the chips inherit the larger body font, and no WebKit scrollbar-hiding rule exists.

- [ ] **Step 3: Implement the minimum CSS change**

Replace the existing tag-filter rules in `src/renderer/styles/dialogue.css` with:

```css
.image-template-tag-filters {
display: flex;
min-width: 0;
gap: 8px;
overflow-x: auto;
padding: 0 1px 2px;
scrollbar-width: none;
-webkit-overflow-scrolling: touch;
}

.image-template-tag-filters::-webkit-scrollbar {
display: none;
}

.image-template-tag-filters button {
display: inline-flex;
flex: 0 0 auto;
align-items: center;
gap: 4px;
min-height: 26px;
border: 1px solid var(--n-hairline);
border-radius: var(--radius-full);
background: var(--n-canvas);
color: var(--n-slate);
padding: 2px 8px;
font-family: inherit;
font-size: 12px;
font-weight: 500;
line-height: 1.2;
letter-spacing: -0.01em;
cursor: pointer;
}

.image-template-tag-filters button b {
color: var(--n-muted);
font-size: 10px;
font-weight: 500;
}

.image-template-tag-filters button:hover,
.image-template-tag-filters button.is-selected {
border-color: var(--n-primary);
background: var(--n-primary-soft);
color: var(--n-primary);
}

.image-template-tag-filters button:hover b,
.image-template-tag-filters button.is-selected b {
color: inherit;
}
```

- [ ] **Step 4: Run focused tests and verify GREEN**

Run:

```bash
npm test -- --run src/renderer/screens/DialogueScreens.test.tsx -t "uses compact tag chips without a visible native horizontal scrollbar"
npm test -- --run src/renderer/imageTemplateTags.test.ts src/renderer/screens/DialogueScreens.test.tsx
```

Expected: the focused contract test passes; the existing image-template tag and dialogue suite reports `172` or more passing tests with zero failures.

- [ ] **Step 5: Run static verification**

Run:

```bash
npm run lint
git diff --check
```

Expected: both commands exit successfully with no TypeScript or whitespace errors.

- [ ] **Step 6: Verify the UI in the browser build**

Run:

```bash
npm run dev:browser
```

Open `http://localhost:3100/`, enter the Image generation screen, and confirm:

- chips render at the approved compact density;
- counts are visually subordinate until hover or selection;
- no native horizontal scrollbar is visible;
- trackpad or shift-wheel horizontal scrolling still reveals off-screen tags;
- selecting a tag still filters the template cards.

- [ ] **Step 7: Commit the implementation**

```bash
git add src/renderer/screens/DialogueScreens.test.tsx src/renderer/styles/dialogue.css
git commit -m "style: polish image template tag filters"
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# Image Template Tag Chip Polish Design

## Goal

Make the image-template tag filters feel quieter, denser, and more consistent with the rest of OfficeDex while removing the visually heavy native horizontal scrollbar.

## Scope

Only the image-template tag filter row is affected. Template filtering behavior, tag counts, selected state semantics, keyboard accessibility, and the image-template card layout remain unchanged.

## Typography and geometry

Use the approved compact editorial direction:

- Label text: `12px`, weight `500`, line-height `1.2`.
- Count text: `10px`, weight `500`.
- Chip minimum height: `26px`.
- Internal horizontal gap: `4px`.
- Chip padding: `2px 8px`.
- Row gap: `8px`.
- Preserve the existing pill radius, border colors, background colors, hover state, and selected state.
- In unselected chips, render the count with the existing muted text token so the label remains the primary scan target.
- In selected and hovered chips, let the count inherit the interactive foreground color for consistent contrast.

## Horizontal overflow

Keep `overflow-x: auto` so trackpads, touch gestures, mouse horizontal scrolling, and keyboard-reachable controls continue to work. Hide the native scrollbar visually across Firefox and WebKit/Blink:

- Firefox: `scrollbar-width: none`.
- WebKit/Blink: hide `::-webkit-scrollbar` by setting its display to `none`.
- Keep momentum scrolling on touch devices.
- Do not clip overflowing tags or replace scrolling with wrapping.

## Accessibility

The controls remain native buttons with their existing `aria-pressed` state. Hiding the scrollbar must not remove scrolling or keyboard access. Text remains at least `10px` for the secondary numeric count and `12px` for the actionable label.

## Verification

- Add a focused stylesheet contract test that reads `dialogue.css` and verifies the approved chip metrics and cross-browser scrollbar-hiding rules.
- Run the focused stylesheet test and the existing image-template tag and dialogue screen tests.
- Run TypeScript linting.
- Start the browser build and visually verify that the tag row uses the compact typography, has no visible native scrollbar, and still scrolls horizontally when content overflows.
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "officedex",
"version": "0.6.7",
"version": "0.6.8",
"description": "The AI desktop workspace for documents, slides, and spreadsheets. Wails v2 + React 19 client for OfficeCLI.",
"license": "GPL-3.0-only",
"homepage": "https://github.com/officecli/officedex#readme",
Expand Down
24 changes: 24 additions & 0 deletions src/renderer/screens/DialogueScreens.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4712,6 +4712,30 @@ describe("DialogueScreen state machine", () => {
expect(screen.queryByText("UGC")).toBeNull();
});

it("uses compact tag chips without a visible native horizontal scrollbar", () => {
const css = readFileSync("src/renderer/styles/dialogue.css", "utf8");
const rowRule = css.match(/\.image-template-tag-filters\s*\{[^}]*\}/s)?.[0] ?? "";
const chipRule = css.match(/\.image-template-tag-filters button\s*\{[^}]*\}/s)?.[0] ?? "";
const countRule = css.match(/\.image-template-tag-filters button b\s*\{[^}]*\}/s)?.[0] ?? "";
const interactiveCountRule = css.match(/\.image-template-tag-filters button:hover b,\s*\.image-template-tag-filters button\.is-selected b\s*\{[^}]*\}/s)?.[0] ?? "";
const webkitScrollbarRule = css.match(/\.image-template-tag-filters::\-webkit-scrollbar\s*\{[^}]*\}/s)?.[0] ?? "";

expect(rowRule).toContain("overflow-x: auto;");
expect(rowRule).toContain("scrollbar-width: none;");
expect(rowRule).toContain("-webkit-overflow-scrolling: touch;");
expect(chipRule).toContain("min-height: 26px;");
expect(chipRule).toContain("gap: 4px;");
expect(chipRule).toContain("padding: 2px 8px;");
expect(chipRule).toContain("font-size: 12px;");
expect(chipRule).toContain("font-weight: 500;");
expect(chipRule).toContain("line-height: 1.2;");
expect(countRule).toContain("color: var(--n-muted);");
expect(countRule).toContain("font-size: 10px;");
expect(countRule).toContain("font-weight: 500;");
expect(interactiveCountRule).toContain("color: inherit;");
expect(webkitScrollbarRule).toContain("display: none;");
});

it("keeps the selected template and edited prompt when the tag filter changes", async () => {
listImageTemplatesSpy.mockResolvedValueOnce([
{ id: 1, slug: "hero", title: "Hero", description: "", promptPreset: "hero", sortOrder: 1, enabled: true, tags: ["Studio"] },
Expand Down
29 changes: 22 additions & 7 deletions src/renderer/styles/dialogue.css
Original file line number Diff line number Diff line change
Expand Up @@ -976,27 +976,37 @@
gap: 8px;
overflow-x: auto;
padding: 0 1px 2px;
scrollbar-width: thin;
scrollbar-width: none;
-webkit-overflow-scrolling: touch;
}

.image-template-tag-filters::-webkit-scrollbar {
display: none;
}

.image-template-tag-filters button {
display: inline-flex;
flex: 0 0 auto;
align-items: center;
gap: 6px;
min-height: 30px;
gap: 4px;
min-height: 26px;
border: 1px solid var(--n-hairline);
border-radius: var(--radius-full);
background: var(--n-canvas);
color: var(--n-slate);
padding: 4px 10px;
font: inherit;
padding: 2px 8px;
font-family: inherit;
font-size: 12px;
font-weight: 500;
line-height: 1.2;
letter-spacing: -0.01em;
cursor: pointer;
}

.image-template-tag-filters button b {
color: inherit;
font-size: 11px;
color: var(--n-muted);
font-size: 10px;
font-weight: 500;
}

.image-template-tag-filters button:hover,
Expand All @@ -1006,6 +1016,11 @@
color: var(--n-primary);
}

.image-template-tag-filters button:hover b,
.image-template-tag-filters button.is-selected b {
color: inherit;
}

.image-template-grid {
display: flex;
gap: 10px;
Expand Down
2 changes: 1 addition & 1 deletion wails.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
},
"info": {
"productName": "OfficeDex",
"productVersion": "0.6.7",
"productVersion": "0.6.8",
"copyright": "",
"comments": "OfficeDex desktop app (Wails v2 + Go)"
}
Expand Down