docs(overlays): create-modal + workshop-section redesign spec
Specifies the create-overlay modal redesign (field reorder, custom radio-list, switch instead of checkbox, drop legacy path hint) and the workshop-items section restructure (drop input-mode radio in favor of autodetected items-vs-collections via batched GetCollectionDetails). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
308fa4eb26
commit
0ffc3fde3d
1 changed files with 191 additions and 0 deletions
|
|
@ -0,0 +1,191 @@
|
|||
# Create Overlay Modal + Workshop Items Section — Redesign
|
||||
|
||||
## Context
|
||||
|
||||
Two UI surfaces still wear the pre-redesign vocabulary even though the global
|
||||
stylesheet was reworked from first principles in `308fa4e`:
|
||||
|
||||
1. **Create-overlay modal** (`templates/overlays.html`, `components.css:114-166`)
|
||||
— uses the old `.modal*` class names, a `<fieldset>` with a native `<legend>`
|
||||
border around the Type radios (looks notched and archaic), native checkbox
|
||||
markup that wraps the input on its own line above the label, and a field
|
||||
order that buries the most-important field (Name) under the most-cluttered
|
||||
one (Type). The "path is generated automatically" hint at the bottom is
|
||||
stale copy from an earlier version where users picked their own paths;
|
||||
paths are now derived from the internal id, so the hint describes behavior
|
||||
the user can no longer influence.
|
||||
|
||||
2. **Workshop items section** on the overlay detail page
|
||||
(`templates/overlay_detail.html:43-67`) — same fieldset-border issue on the
|
||||
Items/Collection radios, two right-aligned buttons (`Add`, `Refresh from
|
||||
Steam`) overlapping with no margin (visual regression), and a forced
|
||||
choice between "items" and "collection" input modes that exists purely to
|
||||
tell the backend which Steam API endpoint to call. The dual mode is also a
|
||||
silent footgun: a user pasting a collection URL into the items field today
|
||||
produces a broken overlay with no warning.
|
||||
|
||||
The goal is to bring both surfaces in line with the redesigned stylesheet
|
||||
*and*, while we're in there, simplify the workshop form structurally (drop the
|
||||
Input-mode radio entirely — see decision rationale below).
|
||||
|
||||
## Decisions
|
||||
|
||||
### Create modal
|
||||
|
||||
- **Field order:** Name → Type → System-wide. Name is the most-typed field
|
||||
and the simplest input; it goes first. Type is the bigger decision but
|
||||
benefits from the user having committed to *something* before they confront
|
||||
three options.
|
||||
- **No fieldset border.** Type label becomes a regular `.field-label` (small
|
||||
uppercase or semibold, matches Name's label).
|
||||
- **Radios become a stacked custom-styled list.** Each row: a circular dot
|
||||
control (outer ring with a colored inner dot when selected), bold label on
|
||||
the right, muted second-line description below the label. All descriptions
|
||||
are visible at once — users can compare options without clicking.
|
||||
Considered and rejected: segmented control — breaks down past 3-4 options
|
||||
(the codebase is plausibly heading toward more overlay types: git, zip,
|
||||
mirror). Considered and rejected: full selectable cards — too much vertical
|
||||
space for what is ultimately a single radio group.
|
||||
- **System-wide checkbox becomes a switch.** Switch sits left-aligned in the
|
||||
row (same left edge as the radio dots), with bold label + muted second-line
|
||||
description to the right — visually consistent with the radio rows above.
|
||||
Switch is conceptually distinct from the type radios (binary on/off vs.
|
||||
one-of-three), so the different control shape reinforces the hierarchy.
|
||||
- **Drop the "path is generated automatically" paragraph.** Legacy copy.
|
||||
- **Buttons keep their current placement** (Cancel + Create, right-aligned in
|
||||
a bordered footer with a slightly muted background) — that part of the
|
||||
current modal already works.
|
||||
|
||||
### Workshop items section
|
||||
|
||||
- **Drop the Input-mode fieldset entirely.** Single textarea accepts any mix
|
||||
of item IDs, item URLs, and collection URLs. Backend autodetects.
|
||||
- **Autodetect strategy** (verified against live Steam API during
|
||||
brainstorming):
|
||||
1. Parse pasted input into a list of IDs (existing helper:
|
||||
`steam_workshop.parse_workshop_input()`).
|
||||
2. **One batched call** to `ISteamRemoteStorage/GetCollectionDetails/v1/`
|
||||
with all parsed IDs. Steam returns one entry per ID:
|
||||
- `result: 1` + `children: [...]` → it's a collection, expand to
|
||||
children IDs.
|
||||
- `result: 9` (k_EResultFileNotFound) → not a collection, keep ID
|
||||
as-is.
|
||||
3. **One batched call** to
|
||||
`ISteamRemoteStorage/GetPublishedFileDetails/v1/` with the flat list
|
||||
of final item IDs (collection-children + non-collection IDs).
|
||||
4. Persist.
|
||||
|
||||
Cost: one extra Steam round-trip on submission (~150 ms), regardless of
|
||||
input size. This is **simpler than today's code**, which has two
|
||||
separate handler branches in `routes/workshop_routes.py:36-99`. The
|
||||
unified flow deletes the `if input_mode == "items" / elif input_mode ==
|
||||
"collection"` branching.
|
||||
|
||||
- **"Refresh from Steam" relocates to a controls row below the items
|
||||
table** (not inside the table). The table ends with its last data row.
|
||||
Below the table sits a single row containing:
|
||||
- Left: a summary hint (`{n} items · {total_size} total` or `0 items`
|
||||
when empty).
|
||||
- Right: a normal-styled `↻ Refresh from Steam` button (disabled when
|
||||
the table is empty).
|
||||
- **Add button placement.** Right-aligned in its own row immediately below
|
||||
the textarea, with proper top margin (today's "no margin" overlap with
|
||||
Refresh is the bug being fixed by moving Refresh).
|
||||
- **Textarea uses monospace font** since pasted content is IDs and URLs.
|
||||
- **Header copy stays** as `Workshop items` (page has other sections —
|
||||
`Files`, `Used by` — and section headers aid scanning).
|
||||
- **Helper text under the label:** "Paste Steam Workshop IDs, item URLs, or
|
||||
collection URLs — one per line. Collections expand automatically." The
|
||||
last sentence is load-bearing — it tells the user they don't need to
|
||||
pre-classify their input.
|
||||
|
||||
## Implementation surface
|
||||
|
||||
### Files to modify
|
||||
|
||||
| Path | Change |
|
||||
|---|---|
|
||||
| `l4d2web/l4d2web/templates/overlays.html` | Reorder fields (Name → Type → System-wide); replace native fieldset+radios+checkbox with new `.field` / `.radio-row` / `.switch-row` markup; drop the path-hint `<p>`; rename `.modal*` → `.dialog*` if aligning with the redesign plan. |
|
||||
| `l4d2web/l4d2web/templates/overlay_detail.html:43-67` | Delete the `<fieldset class="workshop-input-mode">` block; keep textarea but rewrite its surrounding markup as a `.field` block with label + helper text; move `Refresh from Steam` out of its own `<form>` and put it in a single `.table-actions` row below the items table; add a summary span on the left of that row. |
|
||||
| `l4d2web/l4d2web/templates/_overlay_item_table.html` | Verify whether per-row actions exist (e.g., remove-item). Out of scope to change today, but flag for the implementation session: the design preserves whatever per-row actions are there. |
|
||||
| `l4d2web/l4d2web/static/css/components.css` | Add the new component CSS (see below). Existing `.modal*` rules either stay (if we keep old class names) or get renamed to `.dialog*`. Remove `.workshop-input-mode` (no rules to remove — fieldset class has zero CSS today anyway). |
|
||||
| `l4d2web/l4d2web/routes/workshop_routes.py:36-99` | Delete the input-mode branching; unify the handler to (1) parse input, (2) batch-resolve collections, (3) batch-fetch metadata. Existing helpers `parse_workshop_input()`, `resolve_collection()`, `fetch_metadata_batch()` all get reused. |
|
||||
| `l4d2web/l4d2web/steam/steam_workshop.py` | Add (or refactor): a `partition_collections_and_items(ids)` helper that does one `GetCollectionDetails` batch call and returns `(item_ids, collection_id_to_children)`. The exact shape can mirror existing module conventions. |
|
||||
|
||||
### Component CSS to add
|
||||
|
||||
These should live in `components.css` and be reusable beyond just these two
|
||||
surfaces — they form a small set of primitives the rest of the app can adopt
|
||||
as it migrates.
|
||||
|
||||
- `.field` (existing pattern, may already partially exist) — grid container
|
||||
with `gap: var(--space-xs)`; children are `.field-label`, optional
|
||||
`.field-hint`, and the control.
|
||||
- `.radio-row` — flex row, gap `var(--space-s)`, custom radio dot via
|
||||
`::after`; `.radio-row.is-selected` colors the inner dot.
|
||||
- `.radio-list` — grid container for a vertical stack of `.radio-row`s with
|
||||
`gap: var(--space-xs)`. Replaces the `<fieldset>` pattern.
|
||||
- `.switch` + `.switch-row` — pill-shaped toggle, on-state uses
|
||||
`--color-button-primary`. Left-aligned in its row, consistent with
|
||||
`.radio-row` left edge.
|
||||
- `.table-actions` — flex row with `justify-content: space-between`,
|
||||
`align-items: center`, top margin `var(--space-m)`. Sits below a
|
||||
`.table-wrap`.
|
||||
|
||||
Existing tokens (already in `tokens.css`) are sufficient. No new color or
|
||||
spacing tokens needed.
|
||||
|
||||
### Things explicitly **not** changing
|
||||
|
||||
- The `<dialog>` open/close JavaScript (`data-inline-modal-close` handlers)
|
||||
stays as-is.
|
||||
- The overlay detail page's `Files` and `Used by` sections.
|
||||
- The `Delete overlay` / `Rename` actions at the bottom of the page.
|
||||
- The Steam-side caching/refresh logic — only the UI placement of the
|
||||
refresh button is moving.
|
||||
|
||||
## Verification
|
||||
|
||||
End-to-end checks for the implementation session:
|
||||
|
||||
1. **Dev server**: `python scripts/dev-server.py` (not plain `flask run` —
|
||||
the latter misroutes `LEFT4ME_ROOT` on macOS).
|
||||
2. **Create modal**: open the overlays list page, click "Create overlay".
|
||||
- Verify field order: Name → Type → System-wide.
|
||||
- Verify no fieldset border around Type.
|
||||
- Verify custom radio dots fill with accent color on selection.
|
||||
- Verify switch toggles state and visually animates.
|
||||
- Verify no "path is generated automatically" copy anywhere.
|
||||
- Submit with each of the three types in turn; verify the overlay is
|
||||
created with the correct type each time.
|
||||
3. **Workshop section**:
|
||||
- Create a workshop overlay; navigate to its detail page.
|
||||
- Verify only one textarea + one `Add` button (no input-mode radio).
|
||||
- Paste a single item ID (e.g. `3726529483`); click Add. Item appears.
|
||||
- Paste a collection URL (e.g.
|
||||
`https://steamcommunity.com/sharedfiles/filedetails/?id=3724125665`);
|
||||
click Add. The 6 children appear, not the collection itself.
|
||||
- Paste a mix of items and a collection in one submission; verify all
|
||||
resolve correctly.
|
||||
- Verify items table ends at its last row (no internal footer bar).
|
||||
- Verify summary + `↻ Refresh from Steam` sit below the table as a
|
||||
single row.
|
||||
- Click Refresh from Steam; verify metadata refresh fires.
|
||||
4. **Stale-content sweep**: `grep -rn "path is generated automatically"
|
||||
l4d2web/` should return no matches after the change.
|
||||
5. **i18n check** (if applicable): if the project uses i18n strings for
|
||||
these screens, verify the removed/changed strings are cleaned up.
|
||||
|
||||
## Open follow-ups (out of scope)
|
||||
|
||||
- Once the new component CSS (`.radio-row`, `.switch-row`, `.field`,
|
||||
`.table-actions`) lands, sweep the rest of the templates for fieldsets
|
||||
and native checkboxes that could adopt the same vocabulary. Don't do
|
||||
this in the same commit — surface it as a follow-up so the diff for this
|
||||
change stays scoped.
|
||||
- The `superpowers:brainstorming` skill's companion server has an
|
||||
owner-PID detection bug that kills the server when launched via
|
||||
`Bash(run_in_background: true)` on macOS. Workaround during this
|
||||
brainstorm was launching `node server.cjs` directly with
|
||||
`BRAINSTORM_OWNER_PID=1`. Small upstream PR opportunity, unrelated to
|
||||
this codebase.
|
||||
Loading…
Reference in a new issue