Skip to content
Merged
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
5 changes: 4 additions & 1 deletion .codex/skills/devscope-ui-standards/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,10 @@ Read these files before making UI styling changes in the covered surfaces:

## Workflow

- Use the current white-border pattern, not `border-sparkle-border`, for the supported UI surfaces.
- Do not introduce white-border chrome by default on supported UI surfaces.
- Do not use `border-sparkle-border` as the fallback border treatment either.
- Prefer the existing surface language first: transparent borders, subtle fills, and state changes through background, opacity, and motion.
- Only use visible white borders when the surrounding surface already depends on that treatment for structure or separation.
- Check `src/renderer/src/pages/project-details/ProjectDetailsHeaderSection.tsx` before choosing border values.
- Keep default, hover, active, collapsed, and expanded states visually consistent.
- Match the existing subtle divider behavior before introducing a new one.
Expand Down
24 changes: 24 additions & 0 deletions build/installer.nsh
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
!macro customInstall
WriteRegStr SHELL_CONTEXT "Software\Classes\*\shell\DevScopeAir" "" "Open with DevScope Air"
WriteRegStr SHELL_CONTEXT "Software\Classes\*\shell\DevScopeAir" "Icon" "$appExe,0"
WriteRegStr SHELL_CONTEXT "Software\Classes\*\shell\DevScopeAir" "Position" "Top"
WriteRegStr SHELL_CONTEXT "Software\Classes\*\shell\DevScopeAir\command" "" '"$appExe" "%1"'

WriteRegStr SHELL_CONTEXT "Software\Classes\Directory\shell\DevScopeAir" "" "Open with DevScope Air"
WriteRegStr SHELL_CONTEXT "Software\Classes\Directory\shell\DevScopeAir" "Icon" "$appExe,0"
WriteRegStr SHELL_CONTEXT "Software\Classes\Directory\shell\DevScopeAir" "Position" "Top"
WriteRegStr SHELL_CONTEXT "Software\Classes\Directory\shell\DevScopeAir\command" "" '"$appExe" "%1"'

WriteRegStr SHELL_CONTEXT "Software\Classes\Directory\Background\shell\DevScopeAir" "" "Open DevScope Air Here"
WriteRegStr SHELL_CONTEXT "Software\Classes\Directory\Background\shell\DevScopeAir" "Icon" "$appExe,0"
WriteRegStr SHELL_CONTEXT "Software\Classes\Directory\Background\shell\DevScopeAir" "Position" "Top"
WriteRegStr SHELL_CONTEXT "Software\Classes\Directory\Background\shell\DevScopeAir\command" "" '"$appExe" "%V"'
!macroend

!macro customUnInstall
${ifNot} ${isUpdated}
DeleteRegKey SHELL_CONTEXT "Software\Classes\*\shell\DevScopeAir"
DeleteRegKey SHELL_CONTEXT "Software\Classes\Directory\shell\DevScopeAir"
DeleteRegKey SHELL_CONTEXT "Software\Classes\Directory\Background\shell\DevScopeAir"
${endIf}
!macroend
2 changes: 2 additions & 0 deletions docs/current/BRANDING_ASSETS.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ The generator refreshes:
## Runtime Rule

- Dev runs should prefer the blueprint artwork for the literal app icon path and other dev-only shell-facing icon surfaces.
- Dev runs now use the separate runtime identity `DevScope Air-dev`, a dev-only AppUserModelID, and an isolated local profile path so they can run beside the installed app without sharing the same state bucket.
- Packaged Windows builds should use the cleaner release icon set for taskbar, shortcuts, installer, and shell surfaces.
- In-app DevScope ASCII logo components remain the primary UI branding unless a screen explicitly needs image artwork.

Expand All @@ -47,6 +48,7 @@ The generator refreshes:
Before tagging a release, verify:

- dev run shows the blueprint artwork on dev-only branding surfaces
- dev run resolves as `DevScope Air-dev` instead of reusing the installed app identity
- packaged build still uses the clean icon in the window/taskbar
- installer icon and shortcut icon resolve from `resources/icon.ico`
- landing logo still matches the packaged release mark
14 changes: 11 additions & 3 deletions docs/current/CURRENT_CAPABILITIES_MATRIX.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ Last validated against code on March 20, 2026.
## Desktop Shell and System

- Window controls: `Implemented`
- Windows File Explorer shell integration for `Open with DevScope Air` file/folder entry points: `Implemented`
- System overview and detailed system stats: `Implemented`
- Readiness and developer-tooling detection: `Implemented`
- Shared metrics bootstrap, subscribe, and read flows: `Implemented`
Expand Down Expand Up @@ -45,15 +46,22 @@ Last validated against code on March 20, 2026.
- Connect/disconnect and model listing: `Implemented`
- Prompt send and interrupt: `Implemented` (empty composer text falls back to a default send prompt)
- Approval response and user-input response handling: `Implemented`
- Active-plan progress panel and proposed-plan sidebar toggle in the assistant header: `Implemented`
- Active-plan progress panel, proposed-plan sidebar toggle, and inline proposed-plan history blocks with collapsed preview, show-more/show-less controls, sidebar-open action, and explicit implement action: `Implemented`
- Assistant header project Git change summary with total uncommitted +/- stats: `Implemented`
- Assistant composer branch switcher with upward dropdown, branch search, current/default markers, and in-place checkout: `Implemented`
- Pending AI follow-up question panel with inline option response flow: `Implemented`
- Resolved guided-input responses persist as a tool-style `Consulted user` history row with expandable question/answer detail: `Implemented`
- Session project-path association and new thread flow: `Implemented`
- Event subscription and snapshot/status reads: `Implemented`
- Session switching with cached selected-thread hydration: `Implemented`
- Assistant persistence auto-recovers corrupt SQLite state by backing it up, rebuilding, and maintaining a JSON fallback snapshot for recovery: `Implemented`
- Assistant markdown file links and edited-file entries opening in-app preview: `Implemented`
- App-level assistant defaults for model, chat/plan mode, supervised/full-access mode, reasoning level, and fast mode: `Implemented`
- Assistant markdown file links and edited-file entries opening in-app preview, including exact-line opens for file references such as `path/to/file.ts:42` or `#L42`: `Implemented`
- Assistant text inputs expose native right-click spelling suggestions and edit actions: `Implemented`
- Assistant composer exposes optional voice input with mic start/stop control: browser speech streams live on supported runtimes, local Vosk MVP records locally with rolling draft updates plus a final pass on stop, and browser-speech network failures can route directly into highlighted transcription settings: `Implemented`
- Assistant composer image attachments open the file preview renderer directly from the shelf while non-image attachments keep the local attachment preview flow: `Implemented`
- Assistant composer pasted text attachments render as compact paper-card previews and open a dedicated text preview modal: `Implemented`
- Assistant defaults/settings page exposes transcription enablement, browser-vs-local engine selection, local Vosk model download/install prep, and highlight-targeted deep linking from assistant error recovery flows: `Implemented`
- App-level assistant defaults for starter prompt template, model, chat/plan mode, supervised/full-access mode, reasoning level, and fast mode: `Implemented`
- Assistant account overview surface with auth mode, plan, and rate-limit reads: `Implemented`

## Settings and Navigation
Expand Down
11 changes: 10 additions & 1 deletion docs/current/CURRENT_CODEBASE_ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ From `src/renderer/src/App.tsx`, the desktop app currently exposes:

Legacy helper routes still redirect into the live assistant/settings surface instead of serving separate deprecated pages.

The renderer also includes a dedicated `/quick-open` preview route for file-association and shell file launches. That route bypasses the main app shell and now renders inside its own frameless preview window with renderer-owned chrome instead of native Electron frame chrome.

Windows shell folder launches route into `/explorer/:folderPath` with a transient session-level allowance so explicit File Explorer context-menu opens still work even if the optional Explorer sidebar tab is currently disabled in settings.

## Main Process Domain Areas

`src/main/ipc/handlers.ts` registers handlers for:
Expand All @@ -41,6 +45,7 @@ Legacy helper routes still redirect into the live assistant/settings surface ins
- project details and running-process/session views
- file tree, file reads, and file writes
- external terminal launch plus preview-terminal and Python preview flows
- Windows shell launch routing for file previews and folder opens
- Git read/write operations
- desktop update state and install actions

Expand All @@ -55,6 +60,10 @@ The assistant is part of the active app, not a removed feature.

Current assistant capabilities include session lifecycle, model listing, connect/disconnect, prompt send, interrupt, approval responses, user-input responses, project-path association, and event subscription.

Assistant timeline tool-call cards also support path-aware file navigation: edited-file rows and plain file-path lines in tool results can open directly into the shared file-preview renderer.

Assistant conversation status labels are phase-driven from the active thread state; generic pending UI actions should not be treated as thread connection state.

## Shared Contract Model

Two contract groups matter most:
Expand All @@ -72,7 +81,7 @@ The intended architecture direction remains contract-first: define shared contra
- Renderer route state persists key navigation state in local storage and gates optional tabs through settings.
- File preview and project details flows use narrower read operations to avoid unnecessary full reloads.
- Update state is tracked in a dedicated main-process update subsystem instead of being ad hoc renderer state.
- Assistant streaming batches text deltas before projection/broadcast, coalesces renderer event application to animation frames, batches main-to-renderer assistant event IPC, keeps hot persistence writes off the immediate UI interaction path, avoids deep-cloning hydrated thread history on every renderer store update, and relies on incremental off-screen row rendering plus exact history paging to keep long conversations responsive.
- Assistant streaming batches text deltas before projection/broadcast, coalesces renderer event application behind a short delta-flush window plus animation-frame delivery for non-delta events, batches main-to-renderer assistant event IPC, keeps hot persistence writes off the immediate UI interaction path, avoids deep-cloning hydrated thread history on every renderer store update, splits renderer subscriptions so the assistant page shell, conversation pane, and right-side panels do not all rerender on live timeline churn, relies on a sliding tail history window plus per-row deferred rendering to keep long conversations responsive while still allowing explicit older-history expansion, and now persists per-turn usage in a dedicated `assistant_turns` ledger that the thread-details panel fetches on demand instead of inflating the hot assistant snapshot.

## Current Boundary Rules

Expand Down
30 changes: 23 additions & 7 deletions docs/current/UI_BORDER_AND_DIVIDER_STANDARDS.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# UI Border And Divider Standards

Last updated: March 19, 2026
Last updated: March 24, 2026

This document defines the default border and subtle-separator treatment for the current DevScope UI.

Expand All @@ -11,17 +11,25 @@ Use it together with:

## Primary Rule

Use white-border patterns for application UI borders.
Do not use white-border chrome as the default treatment for current UI work.

Do not use `border-sparkle-border` as the default border treatment for current UI work.
Do not use `border-sparkle-border` as the default border treatment either.

Prefer:

- transparent or near-invisible borders for controls that do not need structural separation
- subtle fills and opacity changes for idle states
- motion, tint, and background changes for hover/active emphasis

Use visible white borders only when the surrounding surface already relies on them for containment, grouping, or separation.

## Reference Pattern

Reference component:

- `src/renderer/src/pages/project-details/ProjectDetailsHeaderSection.tsx`

Canonical values:
Canonical values for cases where a visible border is actually needed:

- default border: `border-white/10`
- hover border: `hover:border-white/20`
Expand All @@ -30,6 +38,13 @@ Canonical values:
- bordered surface background: `bg-sparkle-card` or `bg-white/[0.03]`
- hover surface background: `hover:bg-white/[0.03]` or `hover:bg-white/10`

Preferred values for non-structural controls:

- idle border: `border-transparent`
- idle surface: `bg-white/[0.02]` to `bg-white/[0.03]`
- idle text: muted or reduced-opacity foreground
- hover emphasis: background/tint change first, border second

## Where This Applies

- assistant sidebar
Expand All @@ -55,10 +70,11 @@ For subtle separators, dividers, and timeline guides:
When touching these surfaces:

1. check the reference component first
2. keep border tokens consistent across default, hover, active, collapsed, and expanded states
2. do not add a visible white border unless the control actually needs structural definition
3. search for stray `border-sparkle-border` usage in the edited surface
4. verify compact and non-compact modes
5. verify hover and active states
4. keep state styling consistent across default, hover, active, collapsed, and expanded states
5. verify compact and non-compact modes
6. verify hover and active states

## When To Update This Doc

Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,7 @@
"nsis": {
"oneClick": false,
"allowToChangeInstallationDirectory": true,
"include": "build/installer.nsh",
"installerIcon": "resources/icon.ico",
"uninstallerIcon": "resources/icon.ico"
}
Expand Down
30 changes: 29 additions & 1 deletion src/main/assistant/codex-app-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,33 @@ import {
type SessionContext
} from './codex-runtime-protocol'

function toCodexUserInputAnswer(value: unknown): { answers: string[] } {
if (typeof value === 'string') {
return { answers: [value] }
}

if (Array.isArray(value)) {
return { answers: value.filter((entry): entry is string => typeof entry === 'string') }
}

if (value && typeof value === 'object') {
const maybeAnswers = (value as { answers?: unknown }).answers
if (Array.isArray(maybeAnswers)) {
return { answers: maybeAnswers.filter((entry): entry is string => typeof entry === 'string') }
}
}

return { answers: [] }
}

function toCodexUserInputAnswers(
answers: Record<string, string | string[]>
): Record<string, { answers: string[] }> {
return Object.fromEntries(
Object.entries(answers).map(([questionId, value]) => [questionId, toCodexUserInputAnswer(value)])
)
}

export class CodexAppServerRuntime extends EventEmitter {
private readonly sessions = new Map<string, SessionContext>()
private readonly codexBinary = process.platform === 'win32' ? 'codex.cmd' : 'codex'
Expand Down Expand Up @@ -351,7 +378,8 @@ export class CodexAppServerRuntime extends EventEmitter {
if (!pending) throw new Error(`Unknown user-input request: ${requestId}`)

context.pendingUserInputs.delete(requestId)
this.writeMessage(context, { id: pending.jsonRpcId, result: { answers } })
const codexAnswers = toCodexUserInputAnswers(answers)
this.writeMessage(context, { id: pending.jsonRpcId, result: { answers: codexAnswers } })
this.emitRuntime({
eventId: randomUUID(),
type: 'user-input.resolved',
Expand Down
21 changes: 19 additions & 2 deletions src/main/assistant/codex-runtime-events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,23 @@ interface RuntimeEventHandlerDeps {
writeMessage: (context: SessionContext, message: Record<string, unknown>) => void
}

function readResolvedUserInputAnswers(value: unknown): Record<string, string | string[]> {
const rawAnswers = asRecord(value) || {}
return Object.fromEntries(
Object.entries(rawAnswers).map(([questionId, answerValue]) => {
if (typeof answerValue === 'string') return [questionId, answerValue]
if (Array.isArray(answerValue)) {
return [questionId, answerValue.filter((entry): entry is string => typeof entry === 'string')]
}
const answerRecord = asRecord(answerValue)
const nestedAnswers = Array.isArray(answerRecord?.['answers'])
? answerRecord['answers'].filter((entry): entry is string => typeof entry === 'string')
: []
return [questionId, nestedAnswers.length <= 1 ? (nestedAnswers[0] || '') : nestedAnswers]
})
)
}

export function handleStdoutLine(context: SessionContext, line: string, deps: RuntimeEventHandlerDeps): void {
let parsed: JsonRpcMessage
try {
Expand Down Expand Up @@ -274,12 +291,12 @@ function handleNotification(
}

if (method === 'item/tool/requestUserInput/answered') {
const answers = asRecord(payload['answers']) as Record<string, string | string[]> | undefined
const answers = readResolvedUserInputAnswers(payload['answers'])
deps.emitRuntime({
...eventBase,
type: 'user-input.resolved',
requestId: asString(payload['requestId']) || eventBase.itemId,
payload: { answers: answers || {} }
payload: { answers }
})
return
}
Expand Down
Loading