Files
ComfyAssets-ComfyUI_PromptM…/web/js/workspace/library.css
T
Vito Sansevero 4ac9df6006 feat(history): concept negative variants panel
The history's Negative variants panel is the concept's (app.js a598,
style.css 1059-1154): section.surface.timeline-panel with the panel
heading (h2, p.small "Choose a red dot to change the negative and filter
this gallery."), row actions Compare negatives (aria-pressed; its section
is Task F3), + Add negative and the approved Show archived; a .timeline of
.positive-lane and .variant-lane rows (--lane-offset 38 + 47i) with the
"−" .variant-dot, "<name> · N runs" and a .run-dot when the pair's
run_count > 1; and .timeline-lower with the legend and All images for
this positive.

D26: All images for this positive opens the concept dialog with the
embedded, bounded (Load more) media gallery instead of filtering the
shared gallery. The run dot opens Recorded run for the pair's newest
recorded execution, else a Repeat run dialog with the pair's newest
linked image. Backing data:
- library variants carry prompt_pairs.run_count;
- GET /prompt_manager/library/{positive_id}/history/pairs/{pair_id}/newest
  reads the newest run in the positive's exact raw-text scope whose
  current-epoch saved-pair hint still resolves to that pair (the
  get_history_run hint rule), inside the fenced history snapshot.
- workspace-dialogs gains openContentDialog (onClose releases the body).
- mountRecordedHistory returns openRun.

D45 (new deviations):
- a dot is named "Select negative <title>" when no other loaded lane has
  that title, else "Select negative variant N", so names stay unique;
- the bounded "N of M saved negative variants" count stays a visually
  hidden status; "Saved combinations, not an execution timeline." is gone;
- archived lanes (#235) are concept lanes marked .library-variant-archived
  at .6 emphasis with their Restore button; coarse dots keep a 47px
  target; the heading, legend row and All images button wrap at 390px
  and 200% zoom.

Retired CSS: .library-branches, .library-positive-lane,
.library-variant-lane, .library-variant-dot, .library-variants,
.library-panel-heading, .library-variant-footer, .library-variant-summary.

Selector mapping:
- .library-variants -> .library-history .timeline-panel (region "Saved
  negative variants" unchanged)
- .library-branches -> .timeline; .library-positive-lane -> .positive-lane
- .library-variant-lane -> .variant-lane; .library-variant-dot -> .variant-dot
- .library-variant-archived and .library-variant-restore unchanged
- .library-variant-footer / button:last-child -> .timeline-lower / button
- .library-variants > p.muted -> .timeline-panel .panel-heading .small
- "Select negative variant N" -> "Select negative <title>" for titled
  pairs (history, native library, inspector, visual and real-host
  fixtures); untitled pairs unchanged
- lane text "· N linked image(s)" -> "· N run(s)"
- All images for this positive: aria-pressed filter -> dialog "All images
  for this positive"; tests that used it for the whole gallery select the
  lane linking the image, or read the dialog
- variant panel row actions gain Compare negatives first

Tests: new workspace-history "variants panel matches the concept", "run
dot appears only for pairs with more than one run and opens its run",
"all images dialog lists every image for the positive"; new
workspace-library-editor "show archived and restore still work" (all four
failed before the change). Python: variants carry run_count, newest pair
run (DB and route). Unit: content dialog. The history fixture counts two
canonical pairs and links one image to Clean geometry.
2026-10-21 00:29:34 -07:00

97 lines
10 KiB
CSS
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
main.library-main { --library-positive: #73dda9; }
.library-main :where(.library-shelf, .library-history, .library-linked-images) { min-inline-size: 0; }
.library-main .library-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 18px; overflow-anchor: none; }
.library-main .library-row { grid-column: 1 / -1; display: grid; grid-template-columns: inherit; gap: 18px; min-inline-size: 0; }
.library-main .library-chip { display: inline-block; max-inline-size: 100%; padding: 3px 8px; border: 1px solid var(--ca-line); border-radius: 4px; font-size: 10px; color: var(--ca-muted); background: var(--ca-field); overflow-wrap: anywhere; }
.library-main .library-row-spacer { grid-column: 1 / -1; pointer-events: none; }
.library-main .library-continuation { padding-block: 22px; display: flex; flex-wrap: wrap; align-items: center; gap: 12px; }
.library-main .library-status { flex: 1 1 240px; color: var(--ca-muted); }
.library-main .library-surface { padding: 22px; background: var(--ca-surface); border: 1px solid var(--ca-line); border-radius: 7px; margin-block: 18px; }
.library-main .library-shared-gallery { margin-block-start: 24px; }
.library-main .library-variant-context { color: var(--ca-muted); font-size: 12px; }
@media (min-width: 1700px) { .library-main .library-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
@media (max-width: 700px) { .library-main .library-grid { grid-template-columns: minmax(0, 1fr); } .library-main .library-surface { padding: 16px; } }
[data-ca-theme="light"] main.library-main { --library-positive: #176b46; }
/* Role colors and branch geometry follow the approved history surface. */
main.library-main { --library-negative: #ff8c98; }
[data-ca-theme="light"] main.library-main { --library-negative: #b71931; }
.library-main :where(.library-surface, .library-recorded-container, .recorded-history) { min-inline-size: 0; }
.history-positive-lane::before { content: "+"; position: absolute; inset-block-start: -13px; inset-inline-start: 0; border-radius: 50%; inline-size: 25px; block-size: 25px; background: var(--library-positive); color: #172b22; text-align: center; font-size: 14px; line-height: 21px; }
.history-positive-lane > span { position: absolute; inset-block-start: -25px; inset-inline-start: 38px; font-size: 10px; color: var(--library-positive); }
.history-context-branch::before { content: ""; position: absolute; inset-block-start: -25px; inset-inline-start: -30px; inline-size: 30px; block-size: 25px; border-inline-start: 2px solid var(--library-negative); border-block-end: 2px solid var(--library-negative); border-end-start-radius: 22px; }
.history-toolbar { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; margin-block: 14px; }
/* D45: the panel's paging buttons wrap like the recorded toolbar's. */
.ca-tools .library-main :where(.timeline-panel, .history-toolbar) > button { max-inline-size: 100%; white-space: normal; }
.recorded-history > .muted, .history-count, .history-scope, .history-status, .history-legend { font-size: 11px; line-height: 1.65; overflow-wrap: anywhere; }
.library-main .recorded-history > h2 { font-size: 14px; line-height: 1.5; }
.library-main .recorded-history > h2 + p { font-size: 12px; line-height: 1.5; }
.history-scope summary { cursor: pointer; }
.history-timeline { position: relative; box-sizing: border-box; inline-size: 100%; max-inline-size: 100%; max-block-size: 490px; overflow: auto; overscroll-behavior: contain; overflow-anchor: none; border: 1px solid var(--ca-line); border-radius: 6px; background: var(--ca-field); }
.history-canvas { position: relative; min-inline-size: calc(100% - 40px); margin: 18px 20px; }
.history-positive-lane { position: absolute; inset-block-start: 17px; inset-inline: 0; border-block-start: 2px solid var(--library-positive); color: var(--library-positive); font-size: 10px; line-height: 1.6; padding-inline-start: 38px; }
.history-earlier-edge { position: absolute; inset: 0 auto 0 0; inline-size: 1px; }
.history-context-branch { position: absolute; inset-inline-end: 0; block-size: 57px; border-block-start: 2px solid color-mix(in srgb, var(--library-negative) 60%, transparent); }
.history-context-label { position: absolute; inset-inline-start: 43px; inset-block-start: 6px; inline-size: 370px; max-inline-size: calc(100% - 43px); font-size: 10px; line-height: 1.5; color: var(--ca-muted); overflow-wrap: anywhere; display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; line-clamp: 2; overflow: clip; }
.history-run-list { position: absolute; inset: 0; margin: 0; padding: 0; list-style: none; }
.history-run-column { position: absolute; inset-block: 0; inline-size: 64px; pointer-events: none; }
.history-position { position: absolute; inset-block-start: 41px; font-size: 10px; color: var(--ca-muted); }
.ca-tools .history-run-marker { position: absolute; pointer-events: auto; transform: translate(-50%, -50%); inline-size: 12px; block-size: 12px; min-inline-size: 12px; min-block-size: 12px; border: 2px solid var(--library-negative); border-radius: 50%; background: var(--ca-field); padding: 0; color: var(--ca-field); font-size: 16px; line-height: 1; }
.ca-tools .history-context-anchor { inline-size: 29px; block-size: 29px; min-inline-size: 29px; min-block-size: 29px; background: var(--library-negative); color: #38151b; font-size: 12px; }
.history-run-marker[aria-pressed="true"], .history-run-marker:focus-visible { outline: 3px solid var(--ca-focus); outline-offset: 4px; }
.history-run-marker[aria-disabled="true"] { opacity: .6; cursor: progress; }
.recorded-run-dialog { box-sizing: border-box; inline-size: min(1100px, calc(100% - 32px)); max-inline-size: calc(100% - 32px); max-block-size: calc(100dvh - 32px); padding: 22px; border: 1px solid var(--ca-border); border-radius: 7px; color: var(--ca-text); background: var(--ca-surface); overflow: auto; overscroll-behavior: contain; scrollbar-gutter: stable; overflow-wrap: anywhere; }
.recorded-run-dialog::backdrop { background: #0009; }
.recorded-run-header { display: flex; gap: 12px; align-items: center; justify-content: space-between; }
.recorded-run-header h2 { margin: 0; font-size: 20px; }
.recorded-run-body, .recorded-run-body > section { min-inline-size: 0; }
.recorded-run-facts { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px; font-size: 12px; }
.recorded-run-facts dt { color: var(--ca-muted); font-size: 10px; }
.recorded-run-facts dd { margin: 4px 0 0; white-space: pre-wrap; }
.recorded-run-anchors, .recorded-run-mode { display: flex; flex-wrap: wrap; gap: 8px; margin-block: 18px; }
.recorded-run-dialog [aria-pressed="true"] { outline: 2px solid var(--ca-focus); outline-offset: 2px; }
.recorded-run-dialog :where(.muted:not(.prompt-text), .recorded-output-status) { font-size: 11px; line-height: 1.65; }
.recorded-provenance { border-block-start: 1px solid var(--ca-line); padding-block-start: 12px; margin-block: 18px; }
.recorded-provenance summary { cursor: pointer; }
.recorded-run-dialog pre { white-space: pre-wrap; overflow-wrap: anywhere; max-inline-size: 100%; }
.recorded-outputs { margin-block: 24px; }
.recorded-receipts { list-style: decimal; padding-inline-start: 24px; }
.recorded-receipt { border: 1px solid var(--ca-line); border-radius: 5px; padding: 12px; margin-block: 10px; font-size: 12px; }
.recorded-receipt > div { margin-block-end: 10px; }
.recorded-receipt[aria-current="true"] { outline: 2px solid var(--ca-focus); outline-offset: 2px; }
.recorded-current-file { border-block-start: 1px solid var(--ca-line); padding-block-start: 16px; }
@media (max-width: 700px) {
.library-main .library-surface { padding: 22px; }
.recorded-run-dialog { padding: 16px; }
.recorded-run-facts { grid-template-columns: minmax(0, 1fr); }
}
@media (pointer: coarse) {
.recorded-history :where(.history-toolbar button, .history-scope summary) { min-inline-size: 44px; min-block-size: 44px; }
.ca-tools .history-run-marker { inline-size: 44px; block-size: 44px; min-inline-size: 44px; min-block-size: 44px; border: 0; background: transparent; color: transparent; }
.ca-tools .history-run-marker::before { content: ""; position: absolute; inset: 16px; box-sizing: border-box; border: 2px solid var(--library-negative); border-radius: 50%; background: var(--ca-field); }
.ca-tools .history-context-anchor::before { content: "−"; inset: 7.5px; inline-size: 29px; block-size: 29px; line-height: 25px; color: #38151b; background: var(--library-negative); }
/* D45: a coarse pointer reaches the concept dot through a 47px target. */
.ca-tools .library-history .variant-dot::after { content: ""; position: absolute; inset: -9px; }
.ca-tools .recorded-run-dialog :where(button, summary) { min-inline-size: 44px; min-block-size: 44px; }
}
@media (forced-colors: active) {
.history-context-branch, .history-context-branch::before, .history-run-marker, .history-run-marker::before { border-color: CanvasText; }
.history-run-marker[aria-pressed="true"] { outline-color: Highlight; }
}
/* Panel actions follow the concept (style.css 294-300, 540-549); the page heading is the concept's own (Task A5).
These .row-actions rules restyle the concept's own selector for the history panels only (the shelf's card footer
keeps the concept's) and are retired in PR F (history). */
.library-main .library-history .row-actions { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }
.ca-tools .library-main .library-history .row-actions button { min-block-size: 32px; font-size: 12px; max-inline-size: 100%; white-space: normal; }
@media (pointer: coarse) { .ca-tools .library-main .library-history .row-actions button { min-block-size: 40px; } }
/* D45: archived pairs (#235) are concept lanes at reduced emphasis (the .6 disabled-marker opacity); text stays at full contrast. */
.library-history .variant-lane.library-variant-archived::before, .ca-tools .library-history .library-variant-archived .variant-dot { opacity: .6; }
.ca-tools .library-main .library-variant-restore { position: static; margin-inline-start: 8px; min-block-size: 24px; padding: 2px 8px; font-size: 10px; vertical-align: middle; }
@media (pointer: coarse) { .ca-tools .library-main .library-variant-restore { min-block-size: 40px; } }
/* D45: at 390px and 200% zoom the panel heading and the legend row wrap, and the All images button wraps its label,
so the page never scrolls sideways (the retired production footer did the same). */
.library-history .timeline-panel > .panel-heading, .library-history .timeline-lower { flex-wrap: wrap; }
.ca-tools .library-history .timeline-lower > button { /* D45 */ max-inline-size: 100%; white-space: normal; }