Skip to content

web: a chat-first look, light/dark themes and a layout switch for the browser edition - #106

Merged
ndemianc merged 6 commits into
feat/web-editionfrom
feat/web-look
Oct 14, 2026
Merged

ndemianc merged 6 commits into
feat/web-editionfrom
feat/web-look

Conversation

@ndemianc

@ndemianc ndemianc commented Oct 10, 2026 •

Copy link
Copy Markdown
Contributor

Stacked on #105 (feat/web-edition): merge that first, or retarget this at develop after it lands.

What

The browser edition opens on the chat, the way a chat app does, instead of on an IDE with a chat in it — and one click gives the editor layout (Explorer, files, the chat docked on the right).

  • Chat first. A centred greeting with four starters, one rounded composer in a 768 px reading column, the conversation list docked on the left from 1000 px wide.
  • Light and dark follow the system, with the build's own two themes (neutral chrome, the editor one step lighter, one violet accent).
  • Layout switch in the status bar (levelcode.web.layout: chatFirst | split), also as three commands. Opening a folder from the computer switches to the editor layout.
  • Quieter workbench: no Welcome page, no source control / run / extensions entries, no second (workbench) Chat view, no minimap or breadcrumbs.

How, without forking the workbench

Piece Where
Themes + first paint web/theme/levelcode-web-theme, initialColorTheme
Chat skin web/ai-extension/skin.js, skin/chat.{css,js} — three exact insertions into the browser build's copy of media/chat.html, every rule under body.lc-web; the build fails if the page stops having the places it is cut into
Chrome web/chrome.css, configuration defaults (web/lib/config.mjs)
Layout web/workspace/extension.js

docs/WEB.md has a new "The look and the layout" section with the levers, what each one depends on, and what to check on a Code-OSS bump.

The desktop app is unchanged

Nothing under extensions/, branding/ or patches/ is touched; extensions/levelcode-ai/media/chat.html and extensions/levelcode-themes/package.json are byte-identical to the parent. The only reachable path into the desktop gate is the two new unit suites (scripts/test-extensions.sh discovers web/test/unit/*.test.js).

Tests

  • Unit gate: 73 files, on macOS (Node 24) and Linux (Node 18, network refused). New: skin, theme, extensions suites; the layout, main.js helpers and copy rewrites extended. Mutation-checked: each defect put back turns its test red.
  • web/test/e2e-look.mjs (new, 26 checks, 10 consecutive runs green): the skin, the rail, light/dark, no Welcome page, no built-in Chat view, both layouts, docked again after a reload. e2e.mjs 30/30 (release and isolated-origin layouts), e2e-signin-tab.mjs 20/20, e2e-byok.mjs 5/5.
  • An independent five-lens review that ran the build (logic, desktop regression and build integrity, accessibility, visual QA of the chat page in every state, visual QA of the workbench): 44 findings, each independently verified, 0 refuted. The second commit fixes them; the ones left are listed below.

Found by running it, not reading it

  • create() returns a disposable: workbench.commands is undefined, the embedder's command API is the module's commands. My first "hide the built-in Chat" helper ran inside a try and did nothing, and a unit test with a stand-in of the same shape passed.
  • An extension's configurationDefaults outrank the embedder's: levelcode-themes pins the desktop's default theme, so the staged copy has that key removed.
  • A page-level color-scheme made the Markdown preview paint white under a dark theme.
  • modernUI fades the top 24 px of every editor, line one included.

Review round (2026-10-14)

All six review threads are answered in place and resolved.

Verified on 3925003: the unit gate passes, 78 files on Node 24 and on Node 18. In headless Chrome against a release built from it: e2e-look 26/26, e2e 30/30, e2e-signin-tab 20/20, e2e-byok 5/5. Two browser probes, not committed, compared the old and new code:

  • a file group beside the chat: before, two groups were left; now there is one, with every file kept;
  • the layout set in .vscode/settings.json: before, the switch did nothing; now it switches.

Not run in a browser: the timing guard on the moves, and the no-README case (the scratch workspace always has a README).

Not done / not run

  • Phones: the chat page is responsive to 420 px, the workbench around it is not. Safari and Firefox are untested.
  • The workbench's own "Chat: …" palette commands and the menus of features this edition lacks still lead to empty views; the panel toggle opens an empty Terminal (a terminal is the next stage).
  • workbench.experimental.modernUI is Code-OSS's experimental flag; the pinned checkout is the only one run.
  • Whether a closed tab always flushes the Explorer's visibility before the next load was not measured.
  • The Markdown preview rendered an empty body in headless Chrome on the old and the new build alike; not investigated.

Draft: for review, not for merge until #105 is decided.

The browser edition now opens on the chat the way a chat app does, and one click
gives the editor layout (Explorer, files, chat docked on the right):

- web/theme/levelcode-web-theme: "LevelCode Web Dark" / "LevelCode Web Light",
  following the system; initialColorTheme paints the same colours before the
  theme has loaded. levelcode-themes' pinned default is stripped from the staged
  copy (an extension's configurationDefaults outrank the embedder's).
- web/ai-extension/skin*: a centred greeting with starters, a pill composer in a
  768px reading column and the conversation list docked as a rail from 1000px,
  cut into the browser build's COPY of media/chat.html at build time and scoped
  under body.lc-web. The desktop page and its suites are untouched.
- web/chrome.css, config defaults: floating panels, activity bar as a row, no
  source control / run / extensions, no Outline / Timeline, no minimap; the
  workbench's own chat view is told it is disabled; no Welcome walkthrough.
- web/workspace: levelcode.web.layout (chatFirst | split), a status-bar switch,
  three commands, Cmd/Ctrl+Alt+L. A docked layout is docked again after a reload.

Tests: skin, theme, layout and copy unit suites (mutation-checked), main.js's
new helpers, and web/test/e2e-look.mjs (21 checks, headless Chrome).
An agent review that RAN the build (44 findings, each independently
verified) found real defects in the first commit. Fixed:

Layout (web/workspace)
- The switch read the tab model before the extension host had the chat's
  tab, so closing the chat and clicking "Editor + chat" left it undocked:
  wait on onDidChangeTabs, and for the chat to be the active editor.
- A later choice now stops an earlier arrangement still waiting (the
  start-up one resumed and put the chat back on the right).
- A reload's restored empty group is closed on the way to chat-first;
  columns, not list order, say which group is on the left.
- Start-up no longer reveals the Explorer or moves focus after the first
  time in a workspace (workspaceState, not globalState).
- The layout commands and the folder command are registered before the
  store is opened, so a browser that refuses IndexedDB still has them.
- Opening a folder from the computer sets the editor layout.
- No default key binding (Cmd+Alt+L is Find in Selection on macOS).

Chat skin
- Card actions revealed on hover only where there is hover; a tap landed
  on rename/delete. Enter/Space resume a card. aria-modal off the docked
  list; a /sessions-opened overlay no longer reappears as a modal.
- Accent text and the focus ring use the link colour (was 2.2-2.7:1).
- The footer row is no longer dimmed with opacity (it dimmed its popover).
- The rail and the account card take the theme's palette; the sign-in
  dialog scrolls in a short window; the empty state is not clipped; the
  chat paints its own ground; starters make sense with no file open.

Workbench
- Code-OSS's own Chat view is hidden for real: create() returns a
  disposable, so workbench.commands was undefined and the first attempt
  never ran. The gate key is set through the module's commands, again
  after the gate's own write at 5 s. The title-bar toggle is hidden.
- editor.padding.top: 24 (the scroll shadow washed out line one).
- No page-level color-scheme: stock webviews (Markdown preview) were
  white under a dark theme.
- The build id covers the declarative built-ins' sources.

Tests: unit 73 files on macOS and Linux Node 18; e2e-look 26 checks, ten
runs in a row; the other end-to-end checks unchanged. The test helper
retries F1 (a key sent while the chat takes the focus is lost).

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Layout switching has unresolved correctness issues, and some syntax colors have insufficient contrast.

6 open findings
What changed in this PR

Adds a chat-first interface to the browser edition introduced in #105, while keeping desktop sources unchanged.

Changes:

  • Adds browser-only chat styling and system-following light/dark themes.
  • Introduces chat-only and editor/chat layouts with status-bar controls.
  • Updates build staging, documentation, and automated checks.
File Description
web/​workspace/​package.json Declares layout commands and setting.
web/​workspace/​extension.js Implements layout switching and restoration.
web/​theme/​levelcode-web-theme/​themes/​web-light.json Adds the light palette.
web/​theme/​levelcode-web-theme/​themes/​web-dark.json Adds the dark palette.
web/​theme/​levelcode-web-theme/​package.json Registers browser themes.
web/​test/​unit/​workspace.test.js Tests layout behavior.
web/​test/​unit/​theme.test.js Checks theme data and defaults.
web/​test/​unit/​skin.test.js Tests skin insertion and interactions.
web/​test/​unit/​main.test.js Tests browser appearance helpers.
web/​test/​unit/​extensions.test.js Tests extension staging and fingerprints.
web/​test/​unit/​copy.test.js Checks browser-only Welcome suppression.
web/​test/​e2e.mjs Updates startup checks.
web/​test/​e2e-look.mjs Adds browser appearance and layout checks.
web/​test/​e2e-byok.mjs Reuses the palette helper.
web/​test/​cdp.mjs Adds a retrying palette helper.
web/​main.js Applies initial colors and chrome overrides.
web/​lib/​extensions.mjs Supports staging and default removal.
web/​lib/​config.mjs Defines browser appearance defaults.
web/​index.html Removes page-level color-scheme metadata.
web/​chrome.css Hides unsupported workbench controls.
web/​boot.css Removes page-level color-scheme styling.
web/​ai-extension/​skin/​chat.js Adds greetings and conversation-rail behavior.
web/​ai-extension/​skin/​chat.css Styles the browser chat.
web/​ai-extension/​skin.js Injects the skin into staged HTML.
web/​ai-extension/​copy.js Adjusts browser copy and Welcome behavior.
web/​ai-extension/​build.mjs Applies the chat skin during bundling.
scripts/​build-web.mjs Packages and fingerprints appearance assets.
docs/​WEB.md Documents appearance, layouts, and limitations.
CLAUDE.md Adds browser styling guidance.

🧠 Review effort: Balanced


💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread web/theme/levelcode-web-theme/themes/web-dark.json Outdated
Comment thread web/theme/levelcode-web-theme/themes/web-light.json Outdated
Comment thread web/workspace/extension.js Outdated
Comment thread web/workspace/extension.js
Comment thread web/workspace/extension.js Outdated
Comment thread web/workspace/extension.js Outdated
… does

The merge of feat/web-edition replaced the build's list of inputs with a hash of the staging folder.
The test of that list now stages the extensions and hashes the result the same way, and COPY_SKIP —
exported only for the list — goes.
… read in both themes

Both themes' syntax palettes (One Dark / One Light) had colours below WCAG AA's 4.5:1 on their own
editor background. Each one is moved in HSL lightness only (hue and saturation kept, within 2 degrees
after rounding) to the first value that reaches 4.5:1. theme.test.js now holds every tokenColors and
semanticTokenColors foreground of both themes to 4.5:1 on the theme's editor.background (an alpha
colour composited first); it fails on the previous themes, listing each colour.

Dark (editor #1b1c22), review comment 4274117784:
- comments, markup quotes, doc comments / doctype  #5c6370 2.81 -> #7b8493 4.50

Light (editor #ffffff), review comment 4274117904:
- comments, markup quotes                          #a0a1a7 2.58 -> #75767e 4.52
- strings, inherited class, inline/fenced code     #50a14f 3.21 -> #428541 4.50
- regexp, escapes, support types                   #0184bc 4.18 -> #017eb4 4.52
- variables, properties, tags, headings, JSON keys #e45649 3.67 -> #df3425 4.51
- functions, decorators, links                     #4078f2 4.05 -> #326ef1 4.53
- classes, types, CSS tags                         #c18401 3.20 -> #9f6d01 4.50
- constants, attribute names, bold                 #986801 4.87 -> #765101 7.12
  (passed already; darkened with the class colour, which shares its hue and would otherwise be
  indistinguishable from it: CIELAB delta-E 2.9, against 16.2 before and 16.9 now)

Token backgrounds: the editor does not paint them ("Token background colors are currently not
supported", colorThemeSchema.ts), so the light theme's `invalid` scope (white on a #e45649 background)
was white text on the white editor, 1.00:1. `invalid` is now the theme's red text in both themes
(light #df3425 4.51, dark #e06c75 5.32) with no background, and the test fails on any token rule that
sets one.
…of the chat, fills the window in both layouts, and works where a workspace sets it

Review comment 4274117959 (the moves act on whatever is active): whenChatActive resolved on its
timeout as well, so the move commands ran whether or not the chat was the active editor and could move
the file beside it; and in split the second levelcode.ai.focus, after README.md was opened (and became
the editor on show), was followed by the move with no wait at all. Each command that acts on the active
editor (the two moves, and the join below) now goes through one guard: the tab model is read
immediately before it, after waiting up to 1.5 s for the reveal to land; one that cannot be confirmed
is skipped, as is every later one in that arrangement, and so is one overtaken by a newer layout
request while it waited. whenChatActive resolves true/false. The wait for the chat to come to the
front before the window is read at all stays: when the layout is changed by saving the setting from
a settings editor opened in front of the chat, that editor's group is in the tab model until the chat
is focused, and read at once the window looks like two groups already, so nothing is docked (found in
a browser run of this change before it was committed; pinned by a unit case). If that wait times out,
nothing is moved. Tests: chatFirst with the chat never
active does not move; split waits after the README until the chat is active again and does not move if
it never is (also on the reload path); a split waiting to move is dropped when chatFirst is chosen
meanwhile.

Review comment 4274118020 (chatFirst left file groups visible): only empty groups were closed, so a
group with files (a split file editor, files put on the right) kept part of the window. Such groups are
now joined into the chat's group with workbench.action.joinAllGroups, which merges into the active group
and moves the editors in inactive, so no document is closed and the chat stays on show. The remaining
group is then unlocked: a group the chat was docked in is auto-locked, and a lone locked group sends
the next file into a new split. The usual split -> chatFirst switch (the chat alone on the right) is
unchanged. Test: three arrangements with files outside the chat's group are joined and unlocked; no
join when the chat is not active.

Review comment 4274118061 (split in a folder without README.md): with the chat as the only tab nothing
kept the first group, so the move closed it and left one group, auto-locked. An empty untitled document
is opened in column one first (preserveFocus, not a preview) when there is no README and no other tab;
if even that cannot be opened the chat is not moved. The existing test that pinned the old sequence for
this case is changed to the new one.

Review comment 4274118118 (the switch wrote only the user setting): with
levelcode.web.layout in .vscode/settings.json the workspace value wins, so the status item and the
commands did nothing. writeLayout() inspects the setting and updates the workspace value when there is
one, the user's otherwise. A folder-level value is not a third target: the setting is window-scoped,
which VS Code does not read from a multi-root folder (FOLDER_SCOPES), and in a single-folder window
the folder's settings are the workspace's. Tests drive activate() and the registered commands.

The vscode stand-in now always has activeTabGroup: a file shown by showTextDocument is on show until
the chat's reveal lands (a test can hold the reveal back), otherwise the chat is; plus
openTextDocument, inspect() and a workspace layer of settings. On the previous extension.js the 8 new
or changed cases fail and the other 32 pass.
@ndemianc
ndemianc merged commit 86864f3 into feat/web-edition Oct 14, 2026
1 check passed
@ndemianc
ndemianc deleted the feat/web-look branch October 14, 2026 19:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants