Skip to content

Managing selection factors (admin)

In the admin: Factors — the screen this page documents (opens in the running app).

What/why: The product catalog — what this screen manages and why it exists.

The Selection factors screen under /admin/factors manages the inputs the guided flow asks about — the questions (factors) and their answers (options) that drive recommendation and right-sizing. Use it to evolve the questionnaire without a developer: add, rename, describe, or retire factors; manage each factor's options; and map an option to the product attributes it should influence. It is restricted to the data editor capability (edit_data); see admin-access.md.

This is the selection half of the knowledge base. It is distinct from managing-systems.md and managing-attributes.md, which manage the product half (what is compared). Factors feed the guided flow; the option → attribute mappings feed the right-sizing rules.

The factors list (/admin/factors)

The list shows every active factor with its key, applies-to note, and option count.

  • New factor reveals a create form: name, an optional key (defaults to a slug of the name; unique, kebab-case), a description (what the flow asks / why it matters), and an optional applies-to note (e.g. Exterior, Commercial).
  • Edit opens the factor's screen (options + mappings); Retire soft-deletes it.
  • Display order — when there is more than one active factor, a Display-order card lets you arrange the global factor order with ↑/↓ and save. This is the order the default /select flow asks factors in, and the starting arrangement new tracks are seeded from. It does not reorder factors inside existing tracks — each track keeps its own arrangement. See factor ordering.

Editing a factor (/admin/factors/[id])

The edit screen has two parts:

Details

Rename the factor, edit its description, and adjust its applies-to note. The key is immutable once created (it is the stable id the guided flow and mappings reference).

The education field (issue #80) holds the trade-off narrative the guided flow teaches at this factor's step — what the factor means and the trade-off it drives. It is distinct from the short description (the "what the flow asks" line): education is the teaching moment surfaced at the decision point. Leave it blank and the step simply renders without a teaching affordance — no gap, no placeholder.

The Education media field (issue #1679), in the Overview tab beside the education text, is the factor's dedicated teaching visual — an image or an embedded video (paste a YouTube, Vimeo, or Wistia URL). It is deliberately separate from the factor's main image (the Media-tab gallery / illustration): setting or clearing education media never changes the main image, and vice-versa — so a step can teach with a different visual than its hero. Education media is one of the four distinct media roles a factor can carry — primary (hero), thumbnail (a Media-tab usage designation, #1696), education media, and illustration — see Media roles. Paste an unsupported video URL and the field refuses it with an error (it never silently ignores a bad link). The education unit renders this media at the Education placement (below); when it is empty the unit renders text-only. The main image never stands in for education media — it is the step's full-bleed background image, and it renders whenever one is attached, completely independent of the education unit and its placement.

The Education placement field (issue #1479, replacing the #363 "Info disclosure" inference) decides where that Education unit — the education text plus its attached education visual — renders in the flow:

  • Inline (the default; a stored NULL) — the education paragraphs render as a block after the description, with the education visual alongside them. No "?" renders. (The step's full-bleed backdrop is the main image, always — independent of this setting.)
  • In "?" modal — nothing renders inline; a "?" beside the step title opens a modal containing exactly the Education unit and nothing else (#1706): the education visual first (when set), then the education text — no heading, no automatic corpus citations. Want an image-only modal? Leave the text empty — there is no separate with/without-text setting.

The "?" exists iff modal-placed content exists — a factor/option with no education text or visual never shows the affordance (no empty "?"), regardless of the placement setting. There is no content-weight inference; placement is exactly what you set.

Options

Each option is a discrete answer to the factor — e.g. Geography → Coastal, Mountain, Urban. An option can carry its own optional education text ("what choosing this means" — e.g. Coastal → salt air, favor corrosion-resistant frames) and its own Education placement (same inline-or-modal choice as the factor): inline renders the text beneath the option label; modal moves the option's Education unit (text + visual) behind the option's own "?". The option's card thumbnail always remains as a selection aid.

  • Add option: a label (what staff/buyers read), an optional value (the stable token the flow keys on; defaults to a slug of the label), an optional key (defaults to factor-label; unique, kebab-case), optional education, and an Education placement (defaults to inline).
  • Edit changes an option's label, value, education, and placement in place.
  • Remove deletes the option outright (see Retire vs. remove below) — unless something still references it, in which case the delete is refused (below).

An option that is still in use can't be removed

An option carrying an In use badge is referenced by something that would be left pointing at nothing if you deleted it (#1869), so Remove is refused. Two things reference an option without a database constraint to protect them:

  • a track that pre-seeds it as an answer, and
  • a display rule that hides the option or tests for it — either a track's own rule or a global rule that applies to every track.

The confirm dialog names what holds the option before you click, and if the server refuses anyway (someone added a reference in another tab), a dialog lists every dependent with a link to the track that owns it. Nothing is changed by a refused delete. Go resolve the references — remove the pre-seeded answer on the track, or delete/re-point the rule — then remove the option.

To rename or re-scope an option instead, use Edit: editing never breaks a reference, only deleting does.

Finding references that are already broken

Options deleted before this guard existed may have left dangling references behind. Two places report them, and neither repairs anything — whether a stale pre-seed should be dropped or re-pointed is your call, not the tool's:

  • npm run verify:option-refs — lists every dangling reference and exits non-zero when there are any (add --remote to check production).
  • The Structure graph — a warning panel lists the same findings, and any factor whose pre-seeded answer no longer exists is flagged dangling reference on the graph itself.

Options are collapsed — expand the ones you need

Each option is a collapsed panel (#1867). The header alone tells you what it holds — whether education copy is set, how many media items it carries, how many attributes it influences, and a warning badge if it has rule-drift advisories (a collapsed panel never hides an active warning). Click an option's title to expand it; the caret and hover cue mark it as a control, and it works from the keyboard (Tab to it, Enter or Space to toggle).

Several options can be open at once — expanding one never collapses another, so you can compare two side by side. Clicking Edit on a collapsed option expands it too, since the editor lives inside the panel.

Ordering options

Options are offered to buyers in the order shown here. Use the ↑ / ↓ controls to arrange them, then Save option order (the button stays disabled until you actually move something). This is the only place that order is set — the guided flow and every option picker follow it immediately.

Ordering is a data edit: it needs the edit_data capability, and each save is recorded once in the audit trail. Because options have never carried an explicit order before, your first save is what assigns one — before that they are all at position 0 and fall back to insertion order. Ordering one factor's options never affects another factor's.

Media (one surface per option)

Each option carries a single Media widget (#1520) — the same widget every entity page uses:

  • Gallery — the option's attached images/videos, always visible, thumbnails enlarge in place. This is the education visual set: it renders in the guided flow per the option's Education placement.
  • Rights at a glance — an attachment whose rights aren't cleared is badged "won't render publicly" right in the list (only cleared assets reach buyer surfaces). Fix rights from the media library's asset page.
  • Results page role — the image that illustrates this value on the recommendation page. The carrying image is badged results page; click Use on results page on any other image (or open Manage for the suggestion list and library picker) to change it. "No image chosen" means the results page falls back to education copy for this value.

The factor itself has the same widget on its Media tab.

Option → attribute mappings

Under each option, Influences attributes lists the product attributes that option should weigh on. Add mapping picks an attribute from the live catalog and, optionally, a note capturing intent (e.g. "prefer higher corrosion resistance"). Remove clears a mapping.

These mappings are the bridge from the selection side to the product side: choosing Coastal points at attributes like corrosion resistance or material. The mapping only records the influence — the scoring logic that turns it into a recommendation lives in the right-sizing rules (/admin/right-sizing), which read these mappings.

Expected value (optional)

A mapping can optionally record the expected value the option implies — an operator (>=, <=, >, <, =, !=, includes, the same vocabulary rule conditions use) plus a value, e.g. Geography → Coastalcorrosion-resistance = high. For enum-typed attributes the value field suggests the attribute's catalog values; other types take a typed value. Leave both blank for a plain advisory mapping — the operator and value are saved (and cleared) together, and re-saving a mapping replaces its previous expression. The saved expression shows on the mapping's chip (e.g. = Exterior).

A value-carrying mapping does not change which system is recommended — matching stays rules-only. It feeds three surfaces:

  • Illustration ranking — an = expected value names an attribute value (attribute:value), so media tagged with that value can illustrate the buyer's answer on the results page.
  • Corpus retrieval= / includes values become extra query vocabulary, so value-level product language ("Exterior rated") is found, not just the attribute name.
  • Rule-drift advisories — the editor flags a value-carrying mapping that has no corresponding rule (nothing conditions on that option + attribute pair) or whose value contradicts an authored rule's threshold (e.g. mapping expects <= 20 where a rule requires >= 30). Advisories appear under the option's mapping list; they never block saving — they are prompts to reconcile the mapping and the rules.

Dependencies (relevance gating & mutual exclusion)

The Dependencies tab (between Options and Media, #908) authors factor-level dependency rules — global, track-independent facts about the domain (full model: factor-dependencies.md). Rules here apply everywhere — every track and the cold-open /select flow — and gate scoring, not just display. For curation that should affect one track only, use that track's Track display rules tab instead (which layer do I use?):

  • Relevance rules from this factor"when this factor's answer is / is anything except [options] → hide factor Y." The rule lives on its source factor; the target factor shows it read-only under Gated by other factors, with a link back to where it's edited. Multiple rules on the same target must ALL match to hide it.
  • Mutual exclusions — pair one of this factor's options with an option on another factor; picking either side in the buyer flow disables the other. A pair is one rule visible (and removable) from both factors' tabs.

Validation is blocking: a save that would create a cycle (a factor's relevance depending on itself) or a dead end (an option whose exclusions leave another factor unanswerable) is refused with the offending chain named. Pickers only offer active factors/options, and a factor can never gate itself.

Advisories: the banner at the top recomputes on every load and flags drift the saves couldn't catch — e.g. a factor retired after a rule referenced it.

Preview: pick a sample answer to see exactly which factors would hide and which options would disable — it runs the same evaluator the buyer flow uses, so what you see is what buyers get.

Every rule create/remove writes an audit-trail entry (entity type dependency).

Retire vs. remove (what is soft-deleted)

  • Retiring a factor is a soft delete: it drops the factor out of the guided flow immediately but preserves its options and every attribute mapping. Retired factors move to a Retired section; Restore returns them. Nothing is hard-deleted, so the guided flow only ever sees active factors.
  • Removing an option is a hard delete of that one row — a fine-grained edit — and its attribute mappings are removed with it. (Retire operates at the whole-factor level; individual options are edited freely.)

What this maps to

  • List / create / retire / restore → src/routes/admin/factors/+page.server.ts
  • Edit + options + mappings → src/routes/admin/factors/[id]/
  • Persistence + validation → src/lib/server/admin/factors.ts
  • Read layer (guided flow sees active only) → listFactors({ includeRetired }) in src/lib/server/queries.ts; see data-access.md.