Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
112 changes: 112 additions & 0 deletions docs/features/simple-ui/PLAN.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
# Simple Chat Appearance Plan

## Goal

Add a device-scoped Appearance setting that lets users choose between the current Default chat and a new Simple chat style. Simple should be a CSS-only presentation layer over the chat stream and prompt controls, with calmer tool calls and a materially better mobile chat experience.

## Current Code Map

- Appearance settings live in `packages/ui/src/components/settings/appearance-settings-section.tsx`.
- Device UI preferences live in `packages/ui/src/stores/preferences.tsx` under `UiSettings`, with server config persistence already wired through `useConfig()`.
- Theme application lives in `packages/ui/src/lib/theme.tsx`, which already writes `data-theme` to `document.documentElement`.
- Global CSS enters through `packages/ui/src/index.css`, then `tokens.css`, `utilities.css`, `controls.css`, `messaging.css`, `panels.css`, and `markdown.css`.
- Prompt send and stop controls are in `packages/ui/src/components/prompt-input.tsx`; their styles are in `packages/ui/src/styles/messaging/prompt-input.css`.
- The main chat and prompt area are mostly covered by `packages/ui/src/styles/messaging/message-section.css`, `packages/ui/src/styles/messaging/tool-call.css`, `packages/ui/src/styles/messaging/message-base.css`, and `prompt-input.css`.

## Product Design Direction

Scene: a user is running CodeNomad from a phone or narrow browser window while moving between tasks, so the chat should reduce chrome, keep the prompt reachable, and make the latest agent work easier to scan in bright ambient light.

- Keep the Default UI unchanged for existing users.
- Make Simple a restrained chat skin with plain tool calls, compact borders, larger touch targets, and a single calm blue accent used only for primary send.
- Reduce chat visual clutter by removing heavy tool-call backgrounds where spacing and faint dividers are enough.
- Preserve information density on desktop, but make message groups feel like a readable timeline instead of stacked panels.
- Prioritize mobile chat: tool rows compress to a readable inline summary, and the prompt actions sit in a thumb-friendly bottom rail.
- Avoid adding new behavior, new layout state, or alternate component implementations for Simple. The mode should be reversible by switching the setting back to Default.

## Appearance Setting UX

- Add a new card in Appearance above Interaction: `Chat style`.
- Choices: `Default` and `Simple`.
- Scope badge: `Device`, consistent with Theme and Interaction.
- Description copy: `Choose how conversations and prompt controls look on this device.`
- Default choice copy: `Current chat layout and styling.`
- Simple choice copy: `Reduced chat chrome, plain tool calls, and mobile-first prompt controls.`
- Persist as `chatStyle: "default" | "simple"` in `UiSettings`.
- Apply as `data-chat-style="simple"` on `document.documentElement`; remove the attribute for Default or set it to `default` consistently.
- Add i18n keys to every locale file in `packages/ui/src/lib/i18n/messages/*/settings.ts`. If translations are not available, use English placeholders matching the repo's current pattern in several locales.

## Implementation Plan

1. Add preference plumbing.

- Add `export type ChatStylePreference = "default" | "simple"` in `preferences.tsx`.
- Add `chatStyle` to `UiSettings`, `defaultUiSettings`, and `normalizeUiSettings`.
- Expose `setChatStyle` from `useConfig()` using the same `updatePreferences` path as other Appearance settings.
- Keep the default as `default` to avoid behavior changes on upgrade.

2. Apply the selected style globally.

- Add a small UI appearance provider or extend `ThemeProvider` to react to `preferences().chatStyle`.
- Set `document.documentElement.dataset.chatStyle = "simple"` when selected.
- Remove the dataset value or set `default` when not selected.
- Keep this separate from `data-theme`; Simple chat must work with `light`, `dark`, and `system` theme modes.

3. Add Appearance controls.

- In `appearance-settings-section.tsx`, add a `chatStyleOptions` list and render a `settings-choice-grid` card similar to Theme.
- Reuse existing `settings-choice` styles so the new card does not introduce a separate control vocabulary.
- Use icons already in `lucide-solid`, such as `Laptop` for Default and `Sun` for Simple.

4. Add Simple chat CSS as scoped overrides.

- Create new focused files under `packages/ui/src/styles/appearance/`, with `simple-ui.css` as the lean entry import.
- Import it once from `packages/ui/src/index.css` after the existing style entry files so it can override safely.
- Prefix all selectors with `:root[data-chat-style="simple"]` to guarantee Default UI remains unchanged.
- Use existing tokens first. Only add Simple-specific tokens in `tokens.css` if repeated values are needed across more than one area.
- Keep aggregate CSS files lean; do not paste Simple chat rules into `messaging.css`, `controls.css`, or `panels.css`.

5. Refine prompt action presentation.

- Keep existing prompt send and stop behavior unchanged.
- Use Simple chat CSS to make prompt actions compact, tactile, and visually distinct.
- Keep button titles and existing i18n labels unchanged unless product copy changes.

6. Simple chat CSS scope.

- Tokens: softer surface stack, lighter borders, subtler shadows, consistent `12px` to `16px` radii, minimum touch target around `44px` on coarse pointers.
- Chat header: reduce hard borders without changing global tab or sidebar chrome.
- Messages: reduce per-message chrome, make tool calls plain with no background fill, keep disclosure/dropdown actions inline, keep metrics as small pills only when enabled.
- Prompt: make the textarea feel like a bottom composer, keep attachments/history secondary, place stop and send as large square buttons in the bottom rail.
- Mobile: use `dvh`, safe-area padding, `@media (max-width: 640px)`, and existing container width state on `.session-center-column`.

## Simple Chat Design Notes

- Desktop keeps existing app chrome, with simpler message rows in the working area.
- Mobile keeps existing navigation behavior and presents a focused stream with a sticky composer.

## Testing Plan

- Run `npm run typecheck` after preference and component changes.
- Run `npm run build:ui` to catch CSS import and bundling issues.
- Manually verify Default chat before and after toggling Simple.
- Manually verify Simple chat at desktop width, tablet width, and mobile width around 390px.
- Verify the setting persists after reload.
- Verify `light`, `dark`, and `system` theme modes still work with both Interface Style choices.
- Verify keyboard focus remains visible on settings choices, prompt buttons, and inline tool-call controls.
- Verify coarse pointer behavior: prompt buttons are at least 44px and textarea does not trigger iOS input zoom.

## Risks And Constraints

- CSS-only Simple chat means structural mobile improvements are limited to what the current DOM can support.
- Some current CSS files are large, so Simple chat should live in scoped files rather than expanding existing monoliths.
- Broad selectors could accidentally affect Default UI. Every Simple selector should be scoped under `:root[data-chat-style="simple"]` and target chat/prompt surfaces only.
- Dark theme needs a separate visual pass because Simple chat's light-first direction should not force light mode.
- CSS-only Simple chat means prompt icon changes are out of scope unless product explicitly requests a separate markup update.

## Acceptance Criteria

- Appearance has a `Default` vs `Simple` chat style setting.
- Default is selected for existing users and remains visually unchanged.
- Simple chat is activated only through the new data attribute and CSS overrides.
- Simple improves mobile chat readability and thumb reach without removing existing features.
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { createEffect, createMemo, createSignal, For, type Component } from "sol
import { Check, ChevronDown, Laptop, Moon, Sun } from "lucide-solid"
import { useI18n } from "../../lib/i18n"
import { useTheme, type ThemeMode } from "../../lib/theme"
import { useConfig } from "../../stores/preferences"
import { useConfig, type ChatStylePreference } from "../../stores/preferences"
import { getBehaviorSettings, type BehaviorSetting } from "../../lib/settings/behavior-registry"

const themeModeOptions: Array<{ value: ThemeMode; icon: typeof Laptop }> = [
Expand All @@ -12,6 +12,11 @@ const themeModeOptions: Array<{ value: ThemeMode; icon: typeof Laptop }> = [
{ value: "dark", icon: Moon },
]

const chatStyleOptions: Array<{ value: ChatStylePreference; icon: typeof Laptop }> = [
{ value: "default", icon: Laptop },
{ value: "simple", icon: Sun },
]

export const AppearanceSettingsSection: Component = () => {
const { t } = useI18n()
const { themeMode, setThemeMode } = useTheme()
Expand All @@ -31,6 +36,7 @@ export const AppearanceSettingsSection: Component = () => {
setDiagnosticsExpansion,
setThinkingBlocksExpansion,
setToolInputsVisibility,
setChatStyle,
} = useConfig()

const behaviorSettings = createMemo(() =>
Expand Down Expand Up @@ -220,6 +226,11 @@ export const AppearanceSettingsSection: Component = () => {
return t("theme.mode.dark")
}

const chatStyleLabel = (style: ChatStylePreference) => {
if (style === "simple") return t("settings.appearance.chatStyle.option.simple.label")
return t("settings.appearance.chatStyle.option.default.label")
}

return (
<div class="settings-section-stack">
<div class="settings-card">
Expand Down Expand Up @@ -256,6 +267,42 @@ export const AppearanceSettingsSection: Component = () => {
</div>
</div>

<div class="settings-card">
<div class="settings-card-header">
<div>
<h3 class="settings-card-title">{t("settings.appearance.chatStyle.title")}</h3>
<p class="settings-card-subtitle">{t("settings.appearance.chatStyle.subtitle")}</p>
</div>
<span class="settings-scope-badge">{t("settings.scope.device")}</span>
</div>
<div class="settings-choice-grid">
{chatStyleOptions.map((option) => {
const Icon = option.icon
return (
<button
type="button"
class="settings-choice"
data-selected={preferences().chatStyle === option.value ? "true" : "false"}
onClick={() => setChatStyle(option.value)}
>
<span class="settings-choice-icon-wrap">
<Icon class="settings-choice-icon" />
</span>
<span class="settings-choice-copy">
<span class="settings-choice-label">{chatStyleLabel(option.value)}</span>
<span class="settings-choice-description">
{t(`settings.appearance.chatStyle.option.${option.value}.description`)}
</span>
</span>
<span class="settings-choice-check" aria-hidden="true">
<Check class="w-4 h-4" />
</span>
</button>
)
})}
</div>
</div>

<div class="settings-card">
<div class="settings-card-header">
<div>
Expand Down
2 changes: 1 addition & 1 deletion packages/ui/src/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
@import './styles/messaging.css';
@import './styles/panels.css';
@import './styles/markdown.css';
@import './styles/appearance/simple-ui.css';
@tailwind base;
@tailwind components;
@tailwind utilities;
Expand Down Expand Up @@ -59,6 +60,5 @@ body {






6 changes: 6 additions & 0 deletions packages/ui/src/lib/i18n/messages/en/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,12 @@ export const settingsMessages = {
"settings.appearance.theme.option.system": "Match your operating system setting",
"settings.appearance.theme.option.light": "Use the light appearance",
"settings.appearance.theme.option.dark": "Use the dark appearance",
"settings.appearance.chatStyle.title": "Chat style",
"settings.appearance.chatStyle.subtitle": "Choose how conversations and prompt controls look on this device.",
"settings.appearance.chatStyle.option.default.label": "Default",
"settings.appearance.chatStyle.option.default.description": "Current chat layout and styling.",
"settings.appearance.chatStyle.option.simple.label": "Simple",
"settings.appearance.chatStyle.option.simple.description": "Reduced chat chrome, plain tool calls, and mobile-first prompt controls.",
"settings.section.notifications.title": "Notifications",
"settings.section.notifications.subtitle": "Control OS-level notifications for session activity.",
"settings.notifications.permission.granted": "Granted",
Expand Down
6 changes: 6 additions & 0 deletions packages/ui/src/lib/i18n/messages/es/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,12 @@ export const settingsMessages = {
"settings.appearance.theme.option.system": "Match your operating system setting",
"settings.appearance.theme.option.light": "Use the light appearance",
"settings.appearance.theme.option.dark": "Use the dark appearance",
"settings.appearance.chatStyle.title": "Estilo del chat",
"settings.appearance.chatStyle.subtitle": "Elige cómo se ven las conversaciones y los controles del prompt en este dispositivo.",
"settings.appearance.chatStyle.option.default.label": "Predeterminado",
"settings.appearance.chatStyle.option.default.description": "Diseño y estilo actuales del chat.",
"settings.appearance.chatStyle.option.simple.label": "Simple",
"settings.appearance.chatStyle.option.simple.description": "Menos elementos visuales en el chat, llamadas a herramientas sencillas y controles del prompt pensados para móvil.",
"settings.section.notifications.title": "Notifications",
"settings.section.notifications.subtitle": "Control OS-level notifications for session activity.",
"settings.notifications.permission.granted": "Granted",
Expand Down
6 changes: 6 additions & 0 deletions packages/ui/src/lib/i18n/messages/fr/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,12 @@ export const settingsMessages = {
"settings.appearance.theme.option.system": "Match your operating system setting",
"settings.appearance.theme.option.light": "Use the light appearance",
"settings.appearance.theme.option.dark": "Use the dark appearance",
"settings.appearance.chatStyle.title": "Style du chat",
"settings.appearance.chatStyle.subtitle": "Choisissez l'apparence des conversations et des contrôles du prompt sur cet appareil.",
"settings.appearance.chatStyle.option.default.label": "Par défaut",
"settings.appearance.chatStyle.option.default.description": "Mise en page et style actuels du chat.",
"settings.appearance.chatStyle.option.simple.label": "Simple",
"settings.appearance.chatStyle.option.simple.description": "Interface de chat allégée, appels d'outils simples et contrôles du prompt adaptés au mobile.",
"settings.section.notifications.title": "Notifications",
"settings.section.notifications.subtitle": "Control OS-level notifications for session activity.",
"settings.notifications.permission.granted": "Granted",
Expand Down
6 changes: 6 additions & 0 deletions packages/ui/src/lib/i18n/messages/he/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,12 @@ export const settingsMessages = {
"settings.appearance.theme.option.system": "התאם להגדרת מערכת ההפעלה",
"settings.appearance.theme.option.light": "השתמש במראה בהיר",
"settings.appearance.theme.option.dark": "השתמש במראה כהה",
"settings.appearance.chatStyle.title": "סגנון צ'אט",
"settings.appearance.chatStyle.subtitle": "בחרו איך שיחות ופקדי הפרומפט ייראו במכשיר הזה.",
"settings.appearance.chatStyle.option.default.label": "ברירת מחדל",
"settings.appearance.chatStyle.option.default.description": "פריסת הצ'אט והעיצוב הנוכחיים.",
"settings.appearance.chatStyle.option.simple.label": "פשוט",
"settings.appearance.chatStyle.option.simple.description": "פחות מעטפת חזותית בצ'אט, קריאות כלים פשוטות ופקדי פרומפט שמותאמים קודם כל למובייל.",
"settings.section.notifications.title": "התראות",
"settings.section.notifications.subtitle": "שלוט בהתראות ברמת מערכת ההפעלה עבור פעילות סשן.",
"settings.notifications.permission.granted": "ניתן",
Expand Down
6 changes: 6 additions & 0 deletions packages/ui/src/lib/i18n/messages/ja/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,12 @@ export const settingsMessages = {
"settings.appearance.theme.option.system": "Match your operating system setting",
"settings.appearance.theme.option.light": "Use the light appearance",
"settings.appearance.theme.option.dark": "Use the dark appearance",
"settings.appearance.chatStyle.title": "チャットスタイル",
"settings.appearance.chatStyle.subtitle": "このデバイスでの会話とプロンプト操作の見た目を選択します。",
"settings.appearance.chatStyle.option.default.label": "デフォルト",
"settings.appearance.chatStyle.option.default.description": "現在のチャットレイアウトとスタイル。",
"settings.appearance.chatStyle.option.simple.label": "シンプル",
"settings.appearance.chatStyle.option.simple.description": "チャットの装飾を減らし、ツール呼び出しを簡素にし、モバイル優先のプロンプト操作にします。",
"settings.section.notifications.title": "Notifications",
"settings.section.notifications.subtitle": "Control OS-level notifications for session activity.",
"settings.notifications.permission.granted": "Granted",
Expand Down
6 changes: 6 additions & 0 deletions packages/ui/src/lib/i18n/messages/ru/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,12 @@ export const settingsMessages = {
"settings.appearance.theme.option.system": "Match your operating system setting",
"settings.appearance.theme.option.light": "Use the light appearance",
"settings.appearance.theme.option.dark": "Use the dark appearance",
"settings.appearance.chatStyle.title": "Стиль чата",
"settings.appearance.chatStyle.subtitle": "Выберите, как будут выглядеть беседы и элементы управления промптом на этом устройстве.",
"settings.appearance.chatStyle.option.default.label": "По умолчанию",
"settings.appearance.chatStyle.option.default.description": "Текущая компоновка и оформление чата.",
"settings.appearance.chatStyle.option.simple.label": "Простой",
"settings.appearance.chatStyle.option.simple.description": "Меньше визуальных элементов в чате, простые вызовы инструментов и элементы управления промптом с учетом мобильных устройств.",
"settings.section.notifications.title": "Notifications",
"settings.section.notifications.subtitle": "Control OS-level notifications for session activity.",
"settings.notifications.permission.granted": "Granted",
Expand Down
6 changes: 6 additions & 0 deletions packages/ui/src/lib/i18n/messages/zh-Hans/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,12 @@ export const settingsMessages = {
"settings.appearance.theme.option.system": "Match your operating system setting",
"settings.appearance.theme.option.light": "Use the light appearance",
"settings.appearance.theme.option.dark": "Use the dark appearance",
"settings.appearance.chatStyle.title": "聊天样式",
"settings.appearance.chatStyle.subtitle": "选择此设备上对话和提示词控件的显示方式。",
"settings.appearance.chatStyle.option.default.label": "默认",
"settings.appearance.chatStyle.option.default.description": "当前聊天布局和样式。",
"settings.appearance.chatStyle.option.simple.label": "简洁",
"settings.appearance.chatStyle.option.simple.description": "减少聊天界面装饰,简化工具调用,并使用移动优先的提示词控件。",
"settings.section.notifications.title": "Notifications",
"settings.section.notifications.subtitle": "Control OS-level notifications for session activity.",
"settings.notifications.permission.granted": "Granted",
Expand Down
Loading
Loading