diff --git a/docker/frontend/src/App.tsx b/docker/frontend/src/App.tsx
index 6c3a3e0..80bef23 100644
--- a/docker/frontend/src/App.tsx
+++ b/docker/frontend/src/App.tsx
@@ -2864,9 +2864,13 @@ export function Workspace() {
setShowRecipes((v) => !v);
},
},
+ // IMPORT is not a look to open but the way a look gets into the list,
+ // so it stands at the tab's own level, beside the chip that opens the
+ // list — the list itself is a level down, and the way in is not one of
+ // the things it holds.
+ { key: 'recipes-import', label: 'IMPORT .RECIPE', onClick: pickRecipeFile },
// ponytail: no CREATE chip — making a recipe and filing the look on
// the stage are their own rail tabs (SAVE RECENT, CREATE RECIPES).
- // IMPORT is the chip under RECIPES in the tab's own column.
];
case 'favorited':
return saved.length === 0
@@ -3215,11 +3219,6 @@ export function Workspace() {
// but never filed + the bundled presets. A CREATE that a signed-in account
// stored lands in `saved` too, so it is dropped from `created` by name here.
//
- // IMPORT is not one of them: it is not a look to open but the way a look gets
- // into the list, so it stands in the tab's own column under the RECIPES chip
- // that opens the list — the file the phone (or another account) exports, same
- // envelope, same parser, so a look crosses devices intact.
- const importChip: ChipDef = { key: 'recipes-import', label: 'IMPORT .RECIPE', onClick: pickRecipeFile };
const recipeStrip: ChipDef[] = [
...saved.map((entry) => ({
key: `saved-${entry.id}`,
@@ -3385,8 +3384,15 @@ export function Workspace() {
onClick: () => {
setOpenGroup(null);
setOpenParam(null);
+ setShowRecipes(false);
},
},
+ // The RECIPES list is a level of its own, though no group holds it: on a
+ // phone it takes the bar's slot (see .col-sub[data-col="recipes"]), so the
+ // name here is the only way back to the tab's own chips.
+ ...(tab === 'presets' && showRecipes
+ ? [{ key: 'recipes', label: t('sec.recipes'), onClick: () => setShowRecipes(false) }]
+ : []),
...(openGroup ? [{ key: 'strip', label: stripLabel(openGroup), onClick: () => setOpenParam(null) }] : []),
// A ruler is a row, not a strip: its name is the knob's, and the strip it
// came from is the level above — COLOR TEMP under WB.
@@ -3557,13 +3563,7 @@ export function Workspace() {
/>
>
) : (
- <>
-
- {/* IMPORT stands under the chip that opens the RECIPES
- list: a look is imported INTO that list, so the way in
- belongs to the chip, not to the list it fills. */}
- {tab === 'presets' && showRecipes ? : null}
- >
+
)}
>
)}
diff --git a/docker/frontend/src/styles/app.css b/docker/frontend/src/styles/app.css
index cbf6fc1..06298ab 100644
--- a/docker/frontend/src/styles/app.css
+++ b/docker/frontend/src/styles/app.css
@@ -1647,12 +1647,16 @@ input[type="range"] { width: 100%; accent-color: var(--accent); }
them is the path under the bar (.crumb), which names the strip they are —
so a second row of chips over the first would only repeat where the finger
already is, and charge the photo a row for it while a knob is being moved.
- Only the two columns that ARE a strip a chip opened displace the bar: the
- RECIPES list, a photo's history and the mask's own column are an open
- chip's content, not the strip its chip opened, and they keep the bar in
- sight under them. Both wear the bar's slot and the bar's own skin, not the
+ Only the columns that ARE a strip a chip opened displace the bar: the
+ option groups, the watermark, and RECIPES. Each of those is what a chip of
+ theirs opened, so the bar would draw the level the finger just asked for
+ twice — once as the chip that opened it, once as the strip itself — and the
+ second is the one the finger is reaching into. A photo's history and the
+ mask's own column are not that: they are an open chip's content, not the
+ strip its chip opened, and they keep the bar in sight under them. The
+ displacing three wear the bar's slot and the bar's own skin, not the
sub-column's — this row IS the bar now. */
- .col-sub[data-col="options"], .col-sub[data-col="wm"] {
+ .col-sub[data-col="options"], .col-sub[data-col="wm"], .col-sub[data-col="recipes"] {
order: 3;
background: var(--bg);
max-height: 46vh;
@@ -1661,7 +1665,8 @@ input[type="range"] { width: 100%; accent-color: var(--accent); }
the slot, with the bar's rule over it and the same ceiling. */
.col-sub[data-col="wm"] { border-top: 1px solid var(--border-soft); overflow-y: auto; }
.workspace:has(.chips > .col-sub[data-col="options"]) .col-main,
- .workspace:has(.chips > .col-sub[data-col="wm"]) .col-main { display: none; }
+ .workspace:has(.chips > .col-sub[data-col="wm"]) .col-main,
+ .workspace:has(.chips > .col-sub[data-col="recipes"]) .col-main { display: none; }
/* One strip at a time holds here too: the ruler a knob opens is the last
strip of the walk, so it takes the place of the strip that opened it. The