From 0f01c45a061c3789bb8c04105919b4a3b2d78aab Mon Sep 17 00:00:00 2001 From: Bauti Date: Tue, 25 Aug 2026 17:24:10 -0300 Subject: [PATCH 1/3] ai: Stabileo AI becomes a panel, and says it is not finished yet MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three defects and one omission, all reproduced before touching anything. **PRO looked broken.** Opening the assistant took the 3-D viewport from 960×784 to **80×80** — a black screen with the status bar still reporting "8 nodos, 8 barras". The model was there; nothing drew it. The cause is one line: `.app-body` is a flex ROW and `.app-body-pro` is a flex COLUMN, and `AiDrawer` was written for the row — `width: 380px; height: 100%`. As a column item that height took everything and left the model's container with none. It is not patched. The assistant is a panel destination now, so it is no longer a participant in that layout and the collapse cannot happen. Measured after: 960×784 → 960×784, unchanged. **Which section it belongs to: none, and that is the point.** The assistant spans modelling, results and design, so it is not a step of the work — which is exactly what `PRO_TAB_STAGE` already says about Project by mapping it to `''`. `ai` joins it there. A fifth stage would have claimed it IS a step; filing it under ANALYSE would have claimed it only reads results. It opens from the header corner, in both modes and at every width, where the controls that act on the application already live. Being a panel is also what gives it a phone. It had none — the trigger was behind `!isMobile` — and now it is the bottom sheet, draggable, answering the density setting, because that is what happens to any panel. **It says it is unfinished, and behaves like it.** A banner over an input you can still type into is a notice the surface contradicts: the reader believes the box. One constant, `AI_IN_DEVELOPMENT`, disables the field, the send button and the actions, and the note explains what the disabling means — in `--st-warn`, before anything else, the same shape and bargain as `conn.experimentalBanner`. The header button carries a warn dot so the state reads before the panel opens. When it ships, one line changes and no control is left behind still greyed. **Out of Education**, as a condition rather than a deletion, with the reason written down: there are ideas for the assistant helping a teacher BUILD exercises and none for a student solving them, so this is expected to be revisited. Verified absent by all three routes — the URL, the phone, and switching mode in-session. Not done here, and deliberately: the body still carries 96 hardcoded colours and no `--st-*` tokens — two of them, #16213e and #0f3460, are literally from the palette `styles/tokens.css` documents as the old one — and it is untranslated, 8 `t()` calls in 1,265 lines. That is presentation of the CONTENT, not of the container, and it is the long, low-risk part. Local `--grep @smoke`: 317 passed, the same count as before this change. --- web/src/App.svelte | 145 ++++++++++++++------ web/src/components/AiDrawer.svelte | 100 ++++++++++++-- web/src/components/pro/ProPanel.svelte | 6 +- web/src/components/ribbon/BasicPanel.svelte | 3 + web/src/lib/i18n/locales/en.ts | 6 + web/src/lib/i18n/locales/es.ts | 6 + web/src/lib/i18n/locales/pt.ts | 6 + web/src/lib/pro/stages.ts | 10 ++ 8 files changed, 226 insertions(+), 56 deletions(-) diff --git a/web/src/App.svelte b/web/src/App.svelte index 25fc647ba..0e8775321 100644 --- a/web/src/App.svelte +++ b/web/src/App.svelte @@ -114,6 +114,39 @@ * Phone only. On a desktop the panel takes width from a canvas that has * plenty, and the existing framing stays legible. */ + /* ── Stabileo AI, as a panel destination ─────────────────────────────── + * + * Not a container of its own any more. In Basic it is a value of `basicPanel` + * like `results` or `project`; in PRO it is a `proActiveTab` like any other. + * Both already know how to be a side panel on a desktop and the bottom sheet + * on a phone, how to be dragged, and how to answer the density setting — so + * the assistant gets all of that by being one of them rather than by having + * any of it written again. + * + * It also removes the PRO bug at the root: `AiDrawer` was a flex child of + * `.app-body`, which is a ROW in Basic and a COLUMN in PRO. Written for the + * row (`width: 380px; height: 100%`), as a column item it took the full + * height and left the model's container with none — the canvas fell to 80×80 + * and PRO looked broken. A panel cannot do that, because it is not a new + * participant in that layout. + */ + const aiPanelOpen = $derived( + uiStore.appMode === 'pro' + ? uiStore.rightDrawerOpen && uiStore.proActiveTab === 'ai' + : basicPanel === 'ai', + ); + + function openAiPanel() { + if (uiStore.appMode === 'pro') { + if (aiPanelOpen) { uiStore.rightDrawerOpen = false; return; } + uiStore.proActiveTab = 'ai'; + uiStore.rightDrawerOpen = true; + uiStore.proPanelVisible = true; + } else { + openBasicPanel('ai'); + } + } + /* ── PRO's phone camera button ───────────────────────────────────────── * * The stage selector that used to live here moved into the sheet, beside the @@ -1071,6 +1104,37 @@ language — rather than in the ribbon. It configures the application, not the document, which is what everything else in this corner does. --> + + {#if uiStore.appMode === 'basico' || uiStore.appMode === 'pro'} + + {/if} + - {#if !uiStore.isMobile && uiStore.aiDrawerOpen} - - {/if} + {#if !uiStore.isMobile} @@ -1775,11 +1841,16 @@ {/if} -{#if !showLanding && !uiStore.isMobile && !uiStore.embedMode && !uiStore.aiDrawerOpen} - -{/if} + @@ -2372,47 +2443,31 @@ background: var(--st-surface-3); } - .ai-fab { - position: fixed; - bottom: 24px; - /* - Over the canvas, not over the panel. Fixed at the viewport's corner it - covered the bottom-right of the right panel — enough to swallow the last - row of the step-by-step wizard, and enough to intercept clicks meant for - whatever was underneath it. - */ - right: calc(24px + var(--st-right-panel-w, 0px)); - z-index: 100; - width: 48px; - height: 48px; + /* + Stabileo AI's entry point, beside Settings. + ────────────────────────────────────────── + A dot in `--st-warn` on the corner of the glyph, because the state has to + be legible BEFORE the panel opens — a reader who has to open something to + learn it does not work has already spent the trip. + */ + .btn-ai { position: relative; } + .ai-glyph { font-size: 0.95rem; line-height: 1; } + .ai-dev-dot { + position: absolute; + top: 4px; + right: 4px; + width: 6px; + height: 6px; border-radius: 50%; - background: var(--st-surface-2); - border: 2px solid var(--st-hair-strong); - color: var(--st-text); - font-size: 1.1rem; - font-weight: 700; - letter-spacing: 0; - cursor: pointer; - display: flex; - align-items: center; - justify-content: center; - transition: all 0.2s; - box-shadow: 0 4px 12px rgba(0, 0, 0, 0.4); + background: var(--st-warn); } - .ai-fab:hover { - background: var(--st-surface-3); - border-color: var(--st-interactive); - color: var(--st-value); - transform: scale(1.05); - } + /* + The floating △ is gone; Stabileo AI opens from the header corner. Its + styles went with it — a fixed 48 px circle that had to dodge the right + panel's width and hide itself whenever the assistant was open. + */ - .ai-fab.active { - background: var(--st-surface-3); - border-color: var(--st-interactive); - color: var(--st-value); - box-shadow: 0 4px 16px rgba(78, 205, 196, 0.3); - } .btn-help { background: transparent; diff --git a/web/src/components/AiDrawer.svelte b/web/src/components/AiDrawer.svelte index 452b27432..f524aac6e 100644 --- a/web/src/components/AiDrawer.svelte +++ b/web/src/components/AiDrawer.svelte @@ -6,6 +6,26 @@ import type { ModelSnapshot } from '../lib/store/history.svelte'; import { compactSnapshotForAi, isValidReleaseShape, normalizeSnapshotReleases } from '../lib/ai/build-model'; + /** + * `docked` — the body without its own chrome, for rendering inside a panel. + * + * The assistant used to be a 380 px container beside the app. It is a panel + * destination now, so the heading, the ✕ and — on a phone — the sheet and its + * drag all come from the panel. See `BasicPanel` and `ProPanel`. + */ + let { docked = false }: { docked?: boolean } = $props(); + + /** + * The assistant is not usable yet, and every control says so from one place. + * + * A banner that announces "in development" above an input you can still type + * into is a notice its own surface contradicts — the reader believes the box, + * not the label. So this flag disables what would otherwise accept work, and + * the banner explains what the disabling means. One constant, so the day it + * ships there is one line to change and no control left behind still greyed. + */ + const AI_IN_DEVELOPMENT = true; + type AiTab = 'review' | 'explain' | 'query' | 'build'; let activeTab = $state('build'); @@ -527,16 +547,46 @@ } } + /* + * Only the undocked form has its own ✕, and nothing renders that any more — + * the panels provide it. Kept as the one place a standalone drawer would + * close from, should the component ever be mounted on its own again. + */ function close() { uiStore.aiDrawerOpen = false; } - +