From 4d553726103cfa9f984914b92888b22a05c51c86 Mon Sep 17 00:00:00 2001 From: vickeykumar Date: Fri, 9 Oct 2026 08:31:36 +0530 Subject: [PATCH] Maximize moves to the app bar, and Genie can be pinned beside the IDE Maximize is an icon button, first in the app bar before Files, and leaves the editor header. A pin button in Genie's header docks the panel as a column at the right of the workspace, like a side bar, with a grip to resize it (300 to 680px, kept). The IDE is stacked while it is pinned and goes back when it is unpinned, unless the user chose a layout meanwhile. Closing a pinned panel hides it and keeps the pin; Maximize keeps the dock. Under 1100px and on phones the pin is hidden and the panel floats as before. Notes, FAQ, about page and LLD 06 in step. Co-Authored-By: Claude Sonnet 5.5 --- docs/lld/06-frontend.md | 3 +- src/js/src/page/02-layout.js | 43 +++- src/js/test/dock-layout.test.mjs | 99 +++++++++ src/resources/about.html | 2 +- src/resources/chat-widget/src/index.ts | 190 +++++++++++++++++- src/resources/chat-widget/src/widget.css | 39 ++++ src/resources/chat-widget/src/widget.html | 3 + .../chat-widget/src/widgetHtmlString.ts | 2 +- src/resources/css/ui-refresh.css | 49 +++++ src/resources/index.html | 7 +- src/resources/knowledge/genie.md | 4 +- 11 files changed, 431 insertions(+), 10 deletions(-) create mode 100644 src/js/test/dock-layout.test.mjs diff --git a/docs/lld/06-frontend.md b/docs/lld/06-frontend.md index 818fa0ea..4ce88e9d 100644 --- a/docs/lld/06-frontend.md +++ b/docs/lld/06-frontend.md @@ -59,7 +59,8 @@ Since T20 the page script is split into feature files under `src/js/src/page/`, What the parts do, together: -- **Layout.** Split panes (split.js), Maximize (`ToggleFunction`, Esc restores), Stacked or Side by side (`ToggleRotateEditor`), Hide/Show editor (`ToggleEditor` + `syncEditorLayoutUI`), and the phone layout (`applyMobileLayout`). +- **Layout.** Split panes (split.js), Maximize (`ToggleFunction`, Esc restores; an icon button, the first one in the app bar, before Files), Genie pinned beside the IDE (below), Stacked or Side by side (`ToggleRotateEditor`), Hide/Show editor (`ToggleEditor` + `syncEditorLayoutUI`), and the phone layout (`applyMobileLayout`). +- **Genie pinned beside the IDE** (`chat-widget/src/index.ts` "pinned beside the IDE", `02-layout.js`, `#genie-dock` in `index.html`). A pin button in the panel's header (between Peer chat and ×) docks the panel as a column at the right of the workspace row, after the editor/terminal split, like a side bar. The panel element is moved into `#genie-dock` (class `is-docked`, which drops the floating position, size and shadow); a grip on the dock's left edge resizes it (drag, arrow keys, double click for the default 380px; 300px to 680px and at most 60% of the row; `--genie-dock-w`, remembered in `localStorage` `genie-dock-width`). The pin itself is `localStorage` `genie-pinned`. While pinned, the widget fires `genie-pin` ({pinned}) on `window` and `02-layout.js` stacks the editor over the terminal (`SetEditorDirection`; a hidden editor stays hidden); unpinning puts the layout back unless the user chose one meanwhile. × hides a pinned panel and keeps the pin; Ask Genie and Ctrl+K bring it back docked. The dock is inside `#ide-shell`, so Maximize keeps it. Below 1100px or on a phone the pin button is hidden and the panel floats as before (the choice is kept, and the panel docks again when the window is wide enough). - **Editor.** Ace setup, theme persistence, language mode from `data-editor`, `updateEditorContent`, download and upload. `defineOpenreplAceTheme()` registers the default "OpenREPL Dark" Ace theme (`ace/theme/openrepl_dark`, the mockup's colours); every other Ace theme stays in `#select-theme`. The terminal uses the same 14px size as the editor (set in `js/src/xterm.ts`). - **Language switch.** `actionOnchange` fetches `/demo?q=` and renders the demo animation, usage table and links (LLD 04). - **Run and debug.** `CompileandRun`, `RunandDebug` dispatch `optionrun` and `optiondebug`. `ToggleReconnect` reconnects. diff --git a/src/js/src/page/02-layout.js b/src/js/src/page/02-layout.js index ad63a1b4..9a193927 100644 --- a/src/js/src/page/02-layout.js +++ b/src/js/src/page/02-layout.js @@ -11,6 +11,7 @@ window.SaveSelectedNodeToFile = SaveSelectedNodeToFile; window.ToggleEditor = ToggleEditor; window.syncEditorLayoutUI = syncEditorLayoutUI; window.ToggleRotateEditor = ToggleRotateEditor; +window.SetEditorDirection = SetEditorDirection; window.ToggleReconnect = ToggleReconnect; function ToggleFunction() { @@ -21,9 +22,12 @@ function ToggleFunction() { shell.classList.toggle("is-maximized", on); document.body.classList.toggle("ide-maximized", on); var btn = get("#togglescreen-button"); - if (btn) btn.setAttribute("aria-pressed", on ? "true" : "false"); - var label = get("#togglescreen-label"); - if (label) label.textContent = on ? "Restore" : "Maximize"; + if (btn) { + // an icon button: the pressed state swaps the icon (CSS), the words are for the tooltip and screen readers + btn.setAttribute("aria-pressed", on ? "true" : "false"); + btn.setAttribute("aria-label", on ? "Restore the workspace" : "Maximize the workspace"); + btn.title = on ? "Restore the workspace (Esc)" : "Maximize the workspace (Esc to restore)"; + } window.dispatchEvent(new Event('resize')); } @@ -230,6 +234,39 @@ function ToggleRotateEditor() { ToggleEditor(); } +// Puts the editor and the terminal in a direction ('horizontal' side by side, +// 'vertical' stacked) without toggling what is not asked for: a hidden editor +// stays hidden (ToggleRotateEditor would show it). +function SetEditorDirection(dir) { + if (direction === null || direction === dir) return false; + if (einst === null) { + direction = dir; + syncEditorLayoutUI(); + } else { + ToggleRotateEditor(); + } + return true; +} + +// Genie pinned beside the IDE (chat-widget/src/index.ts fires "genie-pin" when +// the pin turns on or off). The IDE is stacked while it is pinned, so that the +// panel has room, and goes back to what it was when Genie is unpinned, unless +// the user chose a layout meanwhile. +var pinLayout = { before: null, set: null }; +window.addEventListener("genie-pin", function (e) { + var pinned = !!(e.detail && e.detail.pinned); + if (pinned) { + if (pinLayout.set === null && direction !== null) { + pinLayout.before = direction; + if (SetEditorDirection("vertical")) pinLayout.set = "vertical"; + else pinLayout.set = direction; // it was stacked already: nothing to give back + } + } else if (pinLayout.set !== null) { + if (direction === pinLayout.set && pinLayout.before && pinLayout.before !== direction) SetEditorDirection(pinLayout.before); + pinLayout = { before: null, set: null }; + } +}); + function ToggleReconnect() { // The execution node is away and the countdown is running (11-terminal-state.js). if (window.awayWaitMs && window.awayWaitMs() > 0) return; diff --git a/src/js/test/dock-layout.test.mjs b/src/js/test/dock-layout.test.mjs new file mode 100644 index 00000000..731f98e6 --- /dev/null +++ b/src/js/test/dock-layout.test.mjs @@ -0,0 +1,99 @@ +// The page's side of pinning Genie beside the IDE (src/page/02-layout.js): +// pinned, the editor goes over the terminal to make room; unpinned, the layout +// goes back to what it was, unless the user chose one in between. The widget +// (chat-widget/src/index.ts) only fires "genie-pin"; this runs the page script +// with a small pretend page and the real layout functions. +import test from "node:test"; +import assert from "node:assert/strict"; +import fs from "node:fs"; +import vm from "node:vm"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; + +const here = path.dirname(fileURLToPath(import.meta.url)); +const source = fs.readFileSync(path.join(here, "..", "src", "page", "02-layout.js"), "utf8").replace(/^export \{\};.*$/m, ""); + +function page(startDirection = "horizontal", editorShown = true) { + const listeners = {}; + const classList = { toggle() {}, add() {}, remove() {}, contains: () => false }; + const el = { classList, textContent: "", hidden: false, style: {}, setAttribute() {}, removeAttribute() {} }; + const window = { + addEventListener: (type, fn) => (listeners[type] = fn), + dispatchEvent: () => true, + fire: (pinned) => listeners["genie-pin"]({ detail: { pinned } }), + }; + const ctx = { + window, + document: { body: { classList }, documentElement: { style: { setProperty() {} } }, getElementById: () => el }, + get: () => el, + ismob: () => false, + Split: () => ({ destroy() {} }), + getComputedStyle: () => ({ getPropertyValue: () => "0" }), + setTimeout: () => 0, + Event: class {}, + console, + $: () => ({}), + }; + // the script keeps its state in page globals: window.direction, window.einst + ctx.direction = startDirection; + ctx.einst = editorShown ? { destroy() {} } : null; + vm.createContext(ctx); + vm.runInContext(source, ctx); + // the script's own top-level `window.einst = null` is the page's first state: put ours back + ctx.direction = startDirection; + ctx.einst = editorShown ? { destroy() {} } : null; + return { ctx, fire: window.fire }; +} + +test("pinned, the IDE is stacked; unpinned, it goes back", () => { + const p = page("horizontal"); + p.fire(true); + assert.equal(p.ctx.direction, "vertical"); + p.fire(false); + assert.equal(p.ctx.direction, "horizontal"); +}); + +test("a layout the user chose while pinned is kept when it is unpinned", () => { + const p = page("horizontal"); + p.fire(true); + p.ctx.ToggleRotateEditor(); // the user goes back to side by side + assert.equal(p.ctx.direction, "horizontal"); + p.fire(false); + assert.equal(p.ctx.direction, "horizontal"); +}); + +test("an IDE that was stacked already stays stacked after the pin", () => { + const p = page("vertical"); + p.fire(true); + assert.equal(p.ctx.direction, "vertical"); + p.fire(false); + assert.equal(p.ctx.direction, "vertical"); +}); + +test("a hidden editor stays hidden: pinning only turns the direction", () => { + const p = page("horizontal", false); + p.fire(true); + assert.equal(p.ctx.direction, "vertical"); + assert.equal(p.ctx.einst, null, "the editor was shown by the pin"); + p.fire(false); + assert.equal(p.ctx.direction, "horizontal"); + assert.equal(p.ctx.einst, null); +}); + +test("a pin before the page has chosen a layout does nothing, and later ones work", () => { + const p = page(null); + p.fire(true); + assert.equal(p.ctx.direction, null); + p.fire(false); + p.ctx.direction = "horizontal"; + p.fire(true); + assert.equal(p.ctx.direction, "vertical"); +}); + +test("a second pin event does not forget what the layout was", () => { + const p = page("horizontal"); + p.fire(true); + p.fire(true); + p.fire(false); + assert.equal(p.ctx.direction, "horizontal"); +}); diff --git a/src/resources/about.html b/src/resources/about.html index 4740703d..6907a753 100644 --- a/src/resources/about.html +++ b/src/resources/about.html @@ -67,7 +67,7 @@

What you can do

  • Share a live link so others can watch and type along, or make a code link to a copy of your code.
  • Fork a terminal to run a server and a client side by side.
  • Keep files in your workspace, and sign in to keep them between visits.
  • -
  • Ask Genie, the AI helper, about your code and your terminal output, review its suggestions as a diff, let it carry out a task in Agent mode when you are signed in (it can also work in your terminal, open terminal tabs and manage your files), or right-click selected code to explain, fix, comment or test it. Genie keeps working when you close its panel, and the Ask Genie button tells you when it is done or needs you.
  • +
  • Ask Genie, the AI helper, about your code and your terminal output, review its suggestions as a diff, let it carry out a task in Agent mode when you are signed in (it can also work in your terminal, open terminal tabs and manage your files), or right-click selected code to explain, fix, comment or test it. Genie keeps working when you close its panel, and the Ask Genie button tells you when it is done or needs you. Pin it to dock it beside the editor.
  • Practice with generated interview questions, with a coach for hints, a solution review and complexity.

  • diff --git a/src/resources/chat-widget/src/index.ts b/src/resources/chat-widget/src/index.ts index 21664b98..0a43059f 100644 --- a/src/resources/chat-widget/src/index.ts +++ b/src/resources/chat-widget/src/index.ts @@ -367,6 +367,10 @@ async function init() { .querySelector("[data-chat-widget-button]") ?.addEventListener("click", open); + // a pin kept from earlier: the page stacks the IDE now, and the panel docks when it opens + wireDock(); + announcePin(); + if (config.openOnLoad) { const target = document.querySelector( "[data-chat-widget-button]" @@ -785,6 +789,182 @@ const agentRunner = new AgentRunner( } ); +// ---- pinned beside the IDE ------------------------------------------------------- +// The pin button docks the panel in the workspace row (#genie-dock, index.html), +// to the right of the editor and the terminal, like a side bar. While it is +// pinned the page stacks the editor over the terminal to make room: this file +// only says so, with the "genie-pin" event (js/src/page/02-layout.js listens). +// Closing a pinned panel hides it and keeps the pin. Where there is no room (a +// phone, a window under 1100px) the panel floats as before and the pin button is +// hidden; the choice itself is kept for a wider window. + +const PIN_KEY = "genie-pinned"; +const DOCK_W_KEY = "genie-dock-width"; +const DOCK_MIN_VIEWPORT = 1100; +const DOCK_DEFAULT_W = 380; +const DOCK_MIN_W = 300; + +let pinnedPref = (() => { + try { + return localStorage.getItem(PIN_KEY) === "1"; + } catch (e) { + return false; // storage can be blocked: not remembered + } +})(); +let pinTold = false; // what the page was last told +let resizePending = false; + +function dockEl(): HTMLElement | null { + return document.getElementById("genie-dock"); +} + +function canDock(): boolean { + return !!dockEl() && window.innerWidth >= DOCK_MIN_VIEWPORT && !document.body.classList.contains("is-mobile"); +} + +function wantDock(): boolean { + return pinnedPref && canDock(); +} + +// the editor, the terminal and the terminal tabs fit themselves to a resize +function requestResize() { + if (resizePending) return; + resizePending = true; + requestAnimationFrame(() => { + resizePending = false; + window.dispatchEvent(new Event("resize")); + }); +} + +function announcePin() { + const now = wantDock(); + if (now === pinTold) return; + pinTold = now; + window.dispatchEvent(new CustomEvent("genie-pin", { detail: { pinned: now } })); + requestResize(); +} + +// Puts the panel where it belongs: in the dock when pinned, else on the page. +function placePanel() { + const dock = dockEl(); + if (dock && wantDock()) { + dock.hidden = false; + dock.appendChild(containerElement); + containerElement.classList.add("is-docked"); + document.body.classList.add("genie-docked"); + } else { + containerElement.classList.remove("is-docked"); + document.body.classList.remove("genie-docked"); + if (dock) dock.hidden = true; + document.body.appendChild(containerElement); + } + requestResize(); +} + +function refreshPin() { + const b = document.getElementById("chat-widget__pin"); + if (!b) return; + b.hidden = !canDock(); + b.setAttribute("aria-pressed", String(pinnedPref)); + const words = pinnedPref ? "Unpin Genie (back to floating)" : "Pin Genie beside the IDE"; + b.setAttribute("aria-label", words); + b.setAttribute("title", words); +} + +function setPinned(on: boolean) { + pinnedPref = on; + try { + localStorage.setItem(PIN_KEY, on ? "1" : "0"); + } catch (e) { + // not remembered + } + if (isOpen()) placePanel(); + refreshPin(); + announcePin(); +} + +// the window changed size: dock or float the panel if what fits has changed +function onViewportChange() { + if (isOpen() && containerElement.classList.contains("is-docked") !== wantDock()) placePanel(); + if (isOpen()) refreshPin(); + announcePin(); + // keep a shown dock within what fits (a hidden one measures 0 and keeps its width) + const dock = dockEl(); + if (dock && !dock.hidden && wantDock()) setDockWidth(dock.getBoundingClientRect().width, false); +} + +function setDockWidth(w: number, save: boolean = true): number { + const dock = dockEl(); + const row = dock && dock.parentElement; + const total = row ? row.getBoundingClientRect().width : window.innerWidth; + // (a row that has no width yet, as at start-up, limits nothing) + const max = total > 0 ? Math.max(DOCK_MIN_W, Math.min(680, Math.floor(total * 0.6))) : 680; + const width = Math.round(Math.min(max, Math.max(DOCK_MIN_W, w))); + document.documentElement.style.setProperty("--genie-dock-w", width + "px"); + const grip = document.getElementById("genie-dock-grip"); + if (grip) { + grip.setAttribute("aria-valuemin", String(DOCK_MIN_W)); + grip.setAttribute("aria-valuemax", String(max)); + grip.setAttribute("aria-valuenow", String(width)); + } + if (save) { + try { + localStorage.setItem(DOCK_W_KEY, String(width)); + } catch (e) { + // not remembered + } + } + requestResize(); + return width; +} + +// The grip on the dock's left edge: drag it, or use the arrow keys; a double +// click goes back to the default width. +function wireDock() { + const dock = dockEl(); + const grip = document.getElementById("genie-dock-grip"); + if (!dock || !grip) return; + let stored = DOCK_DEFAULT_W; + try { + stored = parseInt(localStorage.getItem(DOCK_W_KEY) || "", 10) || DOCK_DEFAULT_W; + } catch (e) { + // the default + } + setDockWidth(stored, false); + const widthNow = () => dock.getBoundingClientRect().width; + grip.addEventListener("pointerdown", (ev: PointerEvent) => { + ev.preventDefault(); + try { + grip.setPointerCapture(ev.pointerId); + } catch (e) { + // the drag still works while the pointer stays on the grip + } + const startX = ev.clientX; + const startW = widthNow(); + grip.classList.add("is-dragging"); + document.body.classList.add("genie-dock-dragging"); + const move = (e: PointerEvent) => setDockWidth(startW + (startX - e.clientX), false); + const up = () => { + grip.removeEventListener("pointermove", move); + grip.removeEventListener("pointerup", up); + grip.removeEventListener("pointercancel", up); + grip.classList.remove("is-dragging"); + document.body.classList.remove("genie-dock-dragging"); + setDockWidth(widthNow()); + }; + grip.addEventListener("pointermove", move); + grip.addEventListener("pointerup", up); + grip.addEventListener("pointercancel", up); + }); + grip.addEventListener("keydown", (ev: KeyboardEvent) => { + if (ev.key !== "ArrowLeft" && ev.key !== "ArrowRight") return; + ev.preventDefault(); + setDockWidth(widthNow() + (ev.key === "ArrowLeft" ? 16 : -16)); + }); + grip.addEventListener("dblclick", () => setDockWidth(DOCK_DEFAULT_W)); + window.addEventListener("resize", onViewportChange); +} + // ---- Genie's button while the panel is closed ---------------------------------- // Closing the panel hides it and stops nothing: a task or an answer goes on, and // the Ask Genie buttons (the floating one, the app bar's on phones) say what @@ -1261,7 +1441,7 @@ function open(e?: Event) { document.body.appendChild(optionalBackdrop); } - document.body.appendChild(containerElement); + placePanel(); containerElement.innerHTML = widgetHTML; unread = null; // opening is how the result is seen containerElement.setAttribute("role", "dialog"); @@ -1352,6 +1532,8 @@ function open(e?: Event) { peerchatSwitchElem.addEventListener("change", peerchatSwitchlistener); } + document.getElementById("chat-widget__pin")?.addEventListener("click", () => setPinned(!pinnedPref)); + refreshPin(); document.getElementById("chat-widget__usage-line")?.addEventListener("click", () => { usageDetails = !usageDetails; renderUsage(); @@ -1381,6 +1563,12 @@ function close() { containerElement.remove(); optionalBackdrop.remove(); document.body.classList.remove("genie-open"); + // a pinned panel that is closed leaves its place empty: the IDE has the room back + const dock = dockEl(); + if (dock) dock.hidden = true; + containerElement.classList.remove("is-docked"); + document.body.classList.remove("genie-docked"); + requestResize(); renderActivity(); } diff --git a/src/resources/chat-widget/src/widget.css b/src/resources/chat-widget/src/widget.css index ca401e5c..22742912 100644 --- a/src/resources/chat-widget/src/widget.css +++ b/src/resources/chat-widget/src/widget.css @@ -135,6 +135,45 @@ background: var(--cw-secondary-bg); } +/* Pin: keeps the panel beside the IDE (index.ts: setPinned). Pressed, the pin + is filled and in the accent colour. */ +#chat-widget__pin { + flex-shrink: 0; + width: 30px; + height: 30px; + display: flex; + align-items: center; + justify-content: center; + padding: 0; + color: var(--cw-icon); + background: transparent; + border: 0; + border-radius: 6px; + cursor: pointer; +} +#chat-widget__pin:hover { color: var(--cw-strong); background: var(--cw-secondary-bg); } +#chat-widget__pin[aria-pressed="true"] { color: var(--cw-accent); } +#chat-widget__pin[aria-pressed="true"] svg { fill: currentColor; fill-opacity: 0.25; } +#chat-widget__pin[hidden] { display: none; } + +/* Pinned: the panel fills the dock of the workspace instead of floating. */ +#chat-widget__container.is-docked { + position: relative; + right: auto; + bottom: auto; + z-index: auto; + flex: 1; + width: 100%; + height: 100%; + min-height: 0; + border: 0; + border-radius: 0; + box-shadow: none; + animation: none; +} +/* the header has no room for the "Reads editor + terminal" note beside the pin */ +#chat-widget__container.is-docked #chat-widget__context { display: none; } + /* Peer chat switch: small, next to the title */ .chat-widget__peer { flex-shrink: 0; diff --git a/src/resources/chat-widget/src/widget.html b/src/resources/chat-widget/src/widget.html index 01473f9d..41a08e1c 100644 --- a/src/resources/chat-widget/src/widget.html +++ b/src/resources/chat-widget/src/widget.html @@ -11,6 +11,9 @@ Peer chat + diff --git a/src/resources/chat-widget/src/widgetHtmlString.ts b/src/resources/chat-widget/src/widgetHtmlString.ts index f2960aac..799adf50 100644 --- a/src/resources/chat-widget/src/widgetHtmlString.ts +++ b/src/resources/chat-widget/src/widgetHtmlString.ts @@ -1 +1 @@ -export const widgetHTML = `
    `; +export const widgetHTML = `
    `; diff --git a/src/resources/css/ui-refresh.css b/src/resources/css/ui-refresh.css index e3289936..d67939fb 100644 --- a/src/resources/css/ui-refresh.css +++ b/src/resources/css/ui-refresh.css @@ -473,6 +473,55 @@ kbd { .ide-shell.is-maximized .terminal__row { height: auto; flex: 1; } body.ide-maximized { overflow: hidden; } +/* The maximize button (first in the app bar, before Files): one icon, swapped + while the workspace is maximized. */ +#togglescreen-button .ico-min { display: none; } +#togglescreen-button[aria-pressed="true"] .ico-max { display: none; } +#togglescreen-button[aria-pressed="true"] .ico-min { display: inline; } +#togglescreen-button[aria-pressed="true"] { color: var(--accent-color); } + +/* Genie pinned beside the IDE, in the workspace row (chat-widget/src/index.ts: + pin). The panel is moved in here; the grip on its left edge resizes it. */ +.genie-dock { + position: relative; + flex: none; + display: flex; + flex-direction: column; + width: var(--genie-dock-w, 380px); + min-width: 0; + min-height: 0; + background: var(--ink-950); + border-left: 1px solid var(--ink-700); +} +.genie-dock[hidden] { display: none; } +body.is-mobile .genie-dock { display: none; } +.genie-dock__grip { + position: absolute; + top: 0; + bottom: 0; + left: -4px; + width: 8px; + z-index: 3; + cursor: col-resize; + touch-action: none; +} +.genie-dock__grip::after { + content: ""; + position: absolute; + top: 0; + bottom: 0; + left: 3px; + width: 2px; + background: transparent; + transition: background 0.15s; +} +.genie-dock__grip:hover::after, +.genie-dock__grip:focus-visible::after, +.genie-dock__grip.is-dragging::after { background: var(--accent-color); } +.genie-dock__grip:focus-visible { outline: none; } +body.genie-dock-dragging { cursor: col-resize; user-select: none; } +body.genie-dock-dragging iframe { pointer-events: none; } + /* buttons used across the workspace */ .ide-btn, .app-bar .share-btn, diff --git a/src/resources/index.html b/src/resources/index.html index e3c3bd47..4e7e3f72 100644 --- a/src/resources/index.html +++ b/src/resources/index.html @@ -263,6 +263,7 @@

    {{.Page.Heading}
    +
    @@ -369,7 +370,6 @@

    {{.Page.Heading} -

    @@ -487,6 +487,10 @@

    {{.Page.Heading}

    + +
    @@ -670,6 +674,7 @@

    Questions, answered

    What can Genie do?

    Genie is the AI helper. It reads your editor and terminal, so you can ask why something fails. Its code suggestions show as a diff you accept or reject, with Undo. Open it with Ask Genie or Ctrl+K.

    Can Genie do a task for me?

    Yes, in Agent mode, for signed-in users. Genie writes code, runs it and reads the output step by step. It asks before it changes your editor, runs your program or types in your terminal, and you can press Stop at any time.

    How many Genie requests do I have?

    The line under Genie's message box shows how many requests you have left; click it for the refill rate and your Agent tasks this hour. Requests refill by themselves over time, and signed-in users get more.

    +

    Can I keep Genie open beside my code?

    Yes. Press the pin in Genie's header and it docks at the right of the workspace, like a side bar, with the editor stacked over the terminal. Drag its left edge to resize it. Press the pin again to let it float. It needs a window at least 1100 pixels wide.

    Does Genie keep working if I close its panel?

    Yes. An answer or an Agent task carries on while the panel is closed. The Ask Genie button shows that Genie is working, a green dot when the result is ready, and an amber dot when Genie needs your answer. Open the panel to see it, or to press Stop.

    Can Genie explain or fix just part of my code?

    Yes. Select the code in the editor and right-click it: Explain, Fix problems, Add comments or Write tests. Changes show as a diff you accept or reject.

    Can Genie help me practise without giving the answer?

    Yes. On the Practice page the coach gives up to three hints per question, reviews your solution and states its complexity, and never writes the code for you.

    diff --git a/src/resources/knowledge/genie.md b/src/resources/knowledge/genie.md index 9100b994..0e741c9a 100644 --- a/src/resources/knowledge/genie.md +++ b/src/resources/knowledge/genie.md @@ -1,6 +1,6 @@ --- title: Genie, the AI helper -keywords: genie, ai, assistant, chat, help, ask, explain, fix, suggest, how to use genie, use genie, what can genie do, getting started with genie, model, models, effort, thinking, luna, gpt, gemma, openai, openrouter, ctrl k, requests, requests left, usage, limit, quota, close the panel, background, notification, answer ready +keywords: genie, ai, assistant, chat, help, ask, explain, fix, suggest, how to use genie, use genie, what can genie do, getting started with genie, model, models, effort, thinking, luna, gpt, gemma, openai, openrouter, ctrl k, requests, requests left, pin, dock, side bar, usage, limit, quota, close the panel, background, notification, answer ready link: /privacy.html --- -Genie is OpenREPL's AI helper. Open it with the Ask Genie button or Ctrl+K and type a question. It reads your editor code and the latest terminal output, so you can ask why something fails without pasting anything. Choose the model with the chip beside Send: GPT-6 Luna, which thinks first and has an effort setting, GPT-4o mini, which is quick, or Gemma 4 31B through OpenRouter. Other models, some of them free, can appear in the list when the site owner adds them. Each message uses one request; signed-in users get more. A line under the message box shows how many requests you have left and when they refill. Snippets in answers have Insert and Replace buttons. Right-click selected text to explain it, fix it, comment it or write tests. Signed-in users can switch from Chat to Agent mode. You can close the panel while Genie answers or works on an Agent task: it carries on, and the Ask Genie button shows when it is done or needs you. Genie is optional. Do not paste passwords or keys into it. +Genie is OpenREPL's AI helper. Open it with the Ask Genie button or Ctrl+K and type a question. It reads your editor code and the latest terminal output, so you can ask why something fails without pasting anything. Choose the model with the chip beside Send: GPT-6 Luna, which thinks first and has an effort setting, GPT-4o mini, which is quick, or Gemma 4 31B through OpenRouter. Other models, some of them free, can appear in the list when the site owner adds them. Each message uses one request; signed-in users get more. A line under the message box shows how many requests you have left and when they refill. Snippets in answers have Insert and Replace buttons. Right-click selected text to explain it, fix it, comment it or write tests. Signed-in users can switch from Chat to Agent mode. You can close the panel while Genie answers or works on an Agent task: it carries on, and the Ask Genie button shows when it is done or needs you. The pin in Genie's header docks it beside the editor. Genie is optional. Do not paste passwords or keys into it.