Skip to content

Search-first profile library: tiers, native games, derived descriptions#18

Merged
LordVicky merged 9 commits into
devfrom
fix/library-entry-layout
Jul 12, 2026
Merged

Search-first profile library: tiers, native games, derived descriptions#18
LordVicky merged 9 commits into
devfrom
fix/library-entry-layout

Conversation

@LordVicky

Copy link
Copy Markdown
Owner

Why

The profile library rendered every profile as a flat list. It had three problems:

  1. A layout bug. .secondary-action is redefined with width: 100%; in the entry's flex row the Install button claimed the whole width and, being flex-shrink: 0, gave none back — collapsing the copy column to roughly one character, so descriptions rendered one letter per line.
  2. No way to find a game. Browsing does not scale; searching does.
  3. No support status, and free-text descriptions that could claim effects a profile did not have.

What changed

Search-first library. A search box filters the list in place (option B of four mockups). With an empty query it shows profiles only — listing the 136-game native list would reintroduce the scrolling the search exists to remove.

Status tiers. Each row carries Verified, Community, or Native. Native support is a property of the game, not a profile tier, so it lives in a new native.json published beside the index — 136 games sourced from PCGamingWiki's DualSense_adaptive_trigger_support field (native + limited-native; "requires manual fix" excluded).

Derived descriptions. The author writes one sentence of intent; the capability line (L2 multi-zone · R2 slope · 2 modifiers) is derived from the profile by describeCapabilities(), a pure shared function. build-index.mjs bakes it into index.json using the same code the app runs, so it cannot drift from the profile — and the app describes a profile without downloading it. When weapon variants land in the schema, ported mods will advertise them with no change here.

Installed / Reset. Installs now record meta.libraryFile. The library shows "Installed" instead of an Install button, and the editor offers Reset, which overwrites the profile in place. Routing a reset through importProfile() would take the uniqueName path and leave the edited profile beside a new "Name (2)" — a reinstall, not a reset.

Editor header. Import moved beside Export at matching size, and both icons were the wrong way round. .feature-card-title is a three-column grid, so the header's actions now share one cell; loose buttons overflowed onto an implicit row and pushed Export out of sight.

Safety

  • An absent or malformed tier coerces to community — an unlabelled profile must never present itself as maintainer-verified.
  • A malformed origin is dropped rather than dropping the entry.
  • A failed native.json degrades to no native tags rather than failing the library.
  • meta.libraryFile is validated against the library's file-name pattern: it is interpolated into the fetch URL on reset, so a saved profile must not be able to smuggle a path through it.

Verification

  • 438 tests pass (30 new); both typecheck configs clean.
  • Driven in the real app: search, native rows, the no-profile state, and the header layout (all three buttons on one row at 32px) confirmed on screen, not just in tests.
  • The published mirror serves the new index.json and all 136 native games.

🤖 Generated with Claude Code

LordVicky and others added 9 commits July 12, 2026 11:12
.secondary-action is redefined with width: 100%, so in the library entry's
flex row the Install button claimed the full width and, being flex-shrink: 0,
could not give any back. The copy column has min-width: 0, so it collapsed to
roughly one character and the description rendered one letter per line.

Override the button to width: auto so it sizes to its content.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The library rendered every profile as a flat list with no way to find a game, no
way to say a game needs no profile, and free-text descriptions that could claim
effects a profile did not have.

Search filters the list in place. Each row now carries a status: Verified,
Community, or Native. Native games come from a new native.json published beside
the index -- native support is a property of the game, not a profile tier, so it
does not live on the profile.

Descriptions are split in two: the author writes one sentence of intent, and the
capability line is derived from the profile itself by describeCapabilities(), a
pure shared function. build-index.mjs bakes that line into index.json using the
same code the app uses, so the line cannot drift from the profile, and the app
describes a profile without downloading it. When weapon variants land in the
schema, ported mods advertise them with no change here.

An absent or malformed tier coerces to community: an unlabelled profile must
never present itself as maintainer-verified. A malformed origin is dropped
rather than dropping the entry, and a failed native.json degrades to no native
tags rather than failing the library.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Every native row headlined "Native support", so the bold text carried no
information while the game name sat in a small eyebrow above it. The library is
browsed by game, so the game is now the heading on both row kinds and the
profile name moves beside the author. Drops the eyebrow and its now-unused rule.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Installing recorded nothing about where a profile came from, so the library could
not tell an installed profile from an uninstalled one, and there was no way back
to the published version after editing.

Record meta.libraryFile on install. The library then shows "Installed" instead of
an Install button, and the editor offers Reset for profiles that came from the
library.

Reset overwrites the profile in place via resetToLibrary(), keeping its id and
name. Routing it through importProfile() would have taken the uniqueName path and
left the edited profile untouched beside a new "Name (2)" -- a reinstall, not a
reset. It is behind a confirm dialog because it discards local edits.

libraryFile is validated against the library's own file-name pattern: it is
interpolated into the fetch URL on reset, so a saved profile must not be able to
smuggle a path through it.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Import sat in the profile strip while Export sat above the editor, so the two
halves of the same operation were in different places. Move Import beside Export.

Both now carry .trigger-profiles-transfer-button. .secondary-action sizes buttons
full-width at --action-height for the large test-card actions, which is why Export
towered over the toolbar controls; these are toolbar-scale, so they match the strip
buttons (32px, 12px) instead.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
.feature-card-title is a three-column grid. Adding Import as a sixth loose child
pushed the row past the template, so Export wrapped out of sight. Group the match
source and all three buttons into a single grid cell.

Save then wrapped to its own line as a full-width bar: .primary-action, like
.secondary-action, is width: 100% for the large test-card actions. Size it to its
content in this row.

Import and Export also had their icons the wrong way round -- Import showed the
outbound arrow and Export the inbound one.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Replaces the 14 titles I picked from memory with the wiki's own data, queried
from the Cargo table behind the DualSense list.

Filtered on DualSense_adaptive_trigger_support, not the page's general
Playstation_controller_support column: the latter marks 965 games as supporting
the controller in some way, most of which do nothing with the triggers. Tagging
those Native would tell users no profile is needed for games whose triggers are
inert. The trigger-specific field yields 136.

Kept true (native) and limited (limited native); dropped hackable, which the wiki
renders as "requires manual fix".

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@LordVicky
LordVicky merged commit 32bb85d into dev Jul 12, 2026
2 checks passed
@LordVicky
LordVicky deleted the fix/library-entry-layout branch July 12, 2026 10:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant