mirror of
https://github.com/nexu-io/open-design.git
synced 2026-09-28 05:22:59 +08:00
fix(web): keep the staged attachment band in sight as the prompt scrolls
The scroller sat on .home-hero__prompt-flow, which is also the attachment band's parent — so scrolling a long prompt carried the band away with the text, and an attachment the user had just staged left the screen while they were still writing about it. Move the scroll down to a new .home-hero__prompt-surface holding the prompt and its lead chip and nothing else. That element keeps the position: relative the out-of-flow chip needs as a containing block, so the chip still scrolls with the text it leads rather than pinning itself to the top of the box. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
d3b251de04
commit
2981a2025f
@@ -620,15 +620,16 @@
|
||||
so the SECOND line would still be pushed right — so `.is-chip-inline` takes
|
||||
the row out of flow and indents just the first line past it, using the two
|
||||
custom props HomeHero measures (`--home-hero-chip-w`, and
|
||||
`--home-hero-chip-text-left`, the chip's border+padding). Also owns the
|
||||
prompt's scroll (moved off `.composer-input-editor`) so the out-of-flow chip
|
||||
scrolls together with the text it leads. */
|
||||
`--home-hero-chip-text-left`, the chip's border+padding).
|
||||
It does NOT scroll: the attachment band above the prompt is its child, and a
|
||||
scroller here carried that band away with the text — an attachment the user
|
||||
staged has to stay in sight while they write about it. The scroll sits on
|
||||
`.home-hero__prompt-surface` below, which holds the prompt and the lead chip
|
||||
and nothing else. */
|
||||
.home-hero__prompt-flow {
|
||||
position: relative;
|
||||
display: block;
|
||||
min-width: 0;
|
||||
max-height: var(--home-hero-prompt-max-height, 180px);
|
||||
overflow-y: auto;
|
||||
}
|
||||
.home-hero__active {
|
||||
display: flex;
|
||||
@@ -653,7 +654,9 @@
|
||||
display: none;
|
||||
}
|
||||
/* Staged files + the remaining context chips keep their own band above the
|
||||
prompt; only the template chip below leads the text. */
|
||||
prompt; only the template chip below leads the text. The band sits OUTSIDE
|
||||
the prompt's scroller (see `.home-hero__prompt-surface`), so a long prompt
|
||||
scrolls underneath it and what the user attached stays on screen. */
|
||||
.home-hero__prompt-flow > .home-hero__active-band {
|
||||
margin-bottom: var(--spacing-8);
|
||||
}
|
||||
@@ -727,8 +730,14 @@
|
||||
/* The template chip's own line, anchored to the prompt surface (NOT the flow —
|
||||
the attachment row above must be able to push it down). Stays in flow when
|
||||
the inline treatment is off, so it simply sits above the text. */
|
||||
/* The prompt's own scroller. `position: relative` is load-bearing twice: it is
|
||||
the containing block for the out-of-flow lead chip, and because that
|
||||
containing block IS this scroller, the chip scrolls with the text it leads
|
||||
instead of pinning itself to the top of the box. */
|
||||
.home-hero__prompt-surface {
|
||||
position: relative;
|
||||
max-height: var(--home-hero-prompt-max-height, 180px);
|
||||
overflow-y: auto;
|
||||
}
|
||||
.home-hero__lead-chip {
|
||||
display: flex;
|
||||
@@ -796,8 +805,8 @@
|
||||
display: block;
|
||||
min-width: 0;
|
||||
}
|
||||
/* Scroll moved to .home-hero__prompt-flow (above) — keeping it here would fence
|
||||
the text off from the float in its own formatting context. */
|
||||
/* Scroll moved up to `.home-hero__prompt-surface` — keeping it here would fence
|
||||
the text off from the out-of-flow chip in its own formatting context. */
|
||||
.home-hero__prompt-flow .composer-input-editor {
|
||||
max-height: none;
|
||||
overflow: visible;
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
import { readFileSync } from 'node:fs';
|
||||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
const homeHeroCss = readFileSync(
|
||||
new URL('../../src/styles/home/home-hero.css', import.meta.url),
|
||||
'utf8',
|
||||
);
|
||||
|
||||
function cssDeclarations(selector: string): string {
|
||||
const blocks: string[] = [];
|
||||
const rulePattern = /([^{}]+)\{([^}]*)\}/g;
|
||||
const cssWithoutComments = homeHeroCss.replace(/\/\*[\s\S]*?\*\//g, '');
|
||||
let match: RegExpExecArray | null;
|
||||
while ((match = rulePattern.exec(cssWithoutComments)) !== null) {
|
||||
const selectors = (match[1] ?? '').split(',').map((item) => item.trim());
|
||||
if (selectors.includes(selector)) blocks.push(match[2] ?? '');
|
||||
}
|
||||
if (blocks.length === 0) throw new Error(`Missing CSS block for ${selector}`);
|
||||
return blocks.join('\n');
|
||||
}
|
||||
|
||||
/**
|
||||
* The staged-attachment band and the prompt sit in the same block
|
||||
* (`.home-hero__prompt-flow`), and only ONE of them may scroll. When the flow
|
||||
* owned the scroll, a prompt long enough to overflow carried the band up and
|
||||
* out of the card with it — the user lost sight of the file they had just
|
||||
* attached exactly while they were writing about it.
|
||||
*/
|
||||
describe('HomeHero staged attachment band', () => {
|
||||
it('scrolls the prompt, never the band above it', () => {
|
||||
const flow = cssDeclarations('.home-hero__prompt-flow');
|
||||
expect(flow).not.toMatch(/overflow-y:\s*(auto|scroll)/);
|
||||
expect(flow).not.toMatch(/max-height:/);
|
||||
|
||||
const surface = cssDeclarations('.home-hero__prompt-surface');
|
||||
expect(surface).toMatch(/(?:^|[;\n])\s*overflow-y:\s*auto\s*;/);
|
||||
expect(surface).toMatch(/max-height:\s*var\(--home-hero-prompt-max-height/);
|
||||
});
|
||||
|
||||
// The lead chip is taken out of flow to sit beside the prompt's first line.
|
||||
// Its containing block has to BE the scroller, or it would pin itself to the
|
||||
// top of the box while the line it leads scrolled away underneath.
|
||||
it('keeps the out-of-flow lead chip inside the prompt scroller', () => {
|
||||
expect(cssDeclarations('.home-hero__prompt-surface')).toMatch(
|
||||
/(?:^|[;\n])\s*position:\s*relative\s*;/,
|
||||
);
|
||||
expect(cssDeclarations('.home-hero__prompt-flow.is-chip-inline .home-hero__lead-chip')).toMatch(
|
||||
/(?:^|[;\n])\s*position:\s*absolute\s*;/,
|
||||
);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user