Codex App Parity Skill
Use this skill only when the user explicitly asks for Codex parity work, names codex-app-parity, mentions Codex.app parity, or asks to compare behavior against the installed Codex desktop app.
Do not auto-trigger this skill for ordinary feature work, UI changes, or user-visible behavior changes unless the user explicitly requests that parity workflow.
Objective
Ensure behavior is implemented with Codex.app as the source of truth, then verified with headless Playwright and screenshots.
Project Instructions
Repo Knowledge Maintenance
For user-visible Directory, Skills, Apps, Plugins, MCP, or Composio changes in this repo:
- Update the relevant manual test doc under
tests/<domain>/... with verification steps, including light and dark theme checks. Update tests.md only when adding, renaming, or removing a domain folder.
- If the change creates or changes durable behavior/architecture, add or update an
llm-wiki/raw/... source and corresponding llm-wiki/wiki/... concept page.
- Keep
whatToTest.md as a short pending-only checklist; remove items that were actually executed successfully.
- Prefer assertions plus screenshots for browser validation; screenshots alone are not enough.
Codex.app-First Development Policy
For every new feature and every behavior/UI change, treat the installed desktop app as the source of truth:
- App path:
/Applications/Codex.app
- Primary bundle to inspect:
/Applications/Codex.app/Contents/Resources/app.asar
Do not implement first and compare later. Compare first, then implement.
How to Search for Features in Codex.app
Extraction
Extract the app bundle once (reuse if already extracted):
mkdir -p /tmp/codex-app-extracted
npx asar extract "/Applications/Codex.app/Contents/Resources/app.asar" /tmp/codex-app-extracted
Key Directories
| Directory |
Contents |
/tmp/codex-app-extracted/webview/assets/ |
Main frontend bundle (index-*.js) + locale files |
/tmp/codex-app-extracted/.vite/build/ |
Electron main process (main.js, main-*.js, preload.js, worker.js) |
/tmp/codex-app-extracted/package.json |
App metadata, version, entry point |
Searching the Minified Bundle
The main UI bundle is a single large minified JS file at webview/assets/index-*.js. Use Python to search since grep -o with large repeat counts fails on macOS:
python3 -c "
with open('/tmp/codex-app-extracted/webview/assets/index-<hash>.js', 'r') as f:
content = f.read()
idx = content.find('YOUR_SEARCH_TERM')
if idx >= 0:
print(content[max(0, idx-200):idx+500])
"
What to Search For
i18n keys: Search locale files (webview/assets/zh-TW-*.js, webview/assets/en-*.js, etc.) for human-readable labels. Keys follow the pattern component.feature.property (e.g., composer.dictation.tooltip).
Component functions: Minified React components follow patterns like function X4n({prop1:t,prop2:e,...}). Search for the feature's i18n key to find the component that renders it.
API calls and endpoints: Search main process files (.vite/build/main-*.js) for endpoint URLs, auth handling, and IPC channels. Key patterns:
prodApiBaseUrl → production API base (e.g., https://chatgpt.com/backend-api)
devApiBaseUrl → dev API base (e.g., http://localhost:8000/api)
fetch-request / fetch-response → IPC-proxied HTTP calls from renderer to main process
Icon names: Search for icon imports like audiowave-dark.svg, book-open-dark.svg. Icon mapping is in the main bundle around the Hwn=Object.assign({ pattern.
Keyboard shortcuts: Search for CmdOrCtrl+, Cmd+, keydown, keyCode, or specific key names.
Search Strategy
- Start with i18n locale files — they have human-readable labels that identify features.
- Use the i18n key to find the component in the main bundle.
- Trace the component to find hooks/composables, API calls, and event handlers.
- Check the main process bundle for any server-side proxying or Electron IPC handling.
Mandatory CDP Frontend Inspection
For every feature UI or user-visible fix, inspect the live Codex.app frontend over Chrome DevTools Protocol before implementing. Bundle search is still useful, but it is not enough by itself when a visual/interaction surface exists.
Required CDP Evidence
- Connect to Codex.app over CDP.
- Navigate or interact until the relevant feature UI, closest equivalent UI, or broken/fixed state is visible.
- Capture a screenshot under
output/playwright/ with a task-specific filename.
- Record in the final response:
- CDP endpoint/port
- Codex.app target URL/title
- screenshot absolute path
- what was visually confirmed
If the exact UI cannot be reached, capture the closest relevant Codex.app surface and state the gap.
Mandatory Comparison and Fix Iteration
For every feature UI or user-visible fix, compare Codex.app against the web UI before and after implementation.
Required artifacts:
codex-reference: Codex.app CDP screenshot of the target feature UI or closest equivalent.
web-before: current web UI screenshot before code changes, showing the existing gap or missing behavior.
web-after: web UI screenshot after implementation, showing the proposed parity result.
Required comparison notes:
- Before coding, write a short parity gap list from
codex-reference vs web-before.
- After coding, compare
web-after against codex-reference.
- Classify every notable mismatch as:
fixed: matched or acceptably aligned
intentional deviation: documented reason
needs follow-up: not fixed in this task
- If
web-after reveals a fixable mismatch in layout, copy, visibility, interaction, or state handling, do another implementation iteration and capture a new web-after screenshot.
- Do not report completion until the iteration has either resolved the mismatch or documented why it remains.
Use task-specific screenshot names under output/playwright/, for example:
output/playwright/<task>-codex-reference.png
output/playwright/<task>-web-before.png
output/playwright/<task>-web-after.png
Reliable CDP Launch Pattern
Before launching anything new, first check whether a Codex.app CDP endpoint is already available and reusable. Avoid creating additional Codex instances when an existing CDP-enabled instance already exposes a usable app://-/index.html page target.
Preferred reuse check:
for port in 3434 3435 9222 9223; do
if curl -fsS "http://127.0.0.1:$port/json/list" >/tmp/codex-cdp-list.json 2>/dev/null; then
python3 - <<'PY'
import json
from pathlib import Path
rows = json.loads(Path('/tmp/codex-cdp-list.json').read_text())
page = next((row for row in rows if row.get('type') == 'page' and str(row.get('url', '')).startswith('app://-/index.html')), None)
if page:
print(page['webSocketDebuggerUrl'])
PY
if [ -s /tmp/codex-cdp-list.json ]; then
echo "Reusing CDP on port $port"
break
fi
fi
done
If a usable target is found, reuse it and do not launch another Codex instance.
Only if no reusable CDP target exists, prefer running a separate Codex.app debug instance so the user's normal Codex session is not interrupted and the CDP target can stay alive after tests.
In this repo, prefer the maintained helper script first:
bash /Users/igor/Git-projects/codex-web-local/scripts/run-codex-unpacked-debug.sh
The script:
- launches Codex.app from the installed
app.asar under external Electron
- pins the external runtime to
electron@41.2.0
- auto-picks free CDP and Node inspector ports
- verifies the endpoints after launch
- prepares the required native Sparkle shim for external-Electron runs
If the helper script fails, treat the failure as a skill maintenance signal, not just a one-off launch error:
- Inspect the failing shell script and its nearby helper scripts before using a manual fallback.
- Fix durable launcher bugs in the
.sh scripts when the cause is clear and local to the script.
- Re-run the helper after the fix and update this skill with any new reliable launch finding.
- Use a manual launch fallback only when the script cannot be repaired safely in the current task.
Use --verify-only when you only need to confirm whether the current endpoints are still alive.
Use a fresh app instance with its own profile directory:
CDP_PORT=3434
while lsof -i :"$CDP_PORT" >/dev/null 2>&1; do
CDP_PORT=$((CDP_PORT + 1))
done
CDP_PROFILE_DIR="/tmp/codex-cdp-$CDP_PORT"
mkdir -p "$CDP_PROFILE_DIR"
open -na "Codex" --args \
--remote-debugging-port="$CDP_PORT" \
--user-data-dir="$CDP_PROFILE_DIR"
until curl -fsS "http://127.0.0.1:$CDP_PORT/json/list" >/tmp/codex-cdp-list.json; do
sleep 1
done
If Codex.app is already running without CDP, open -a "Codex" --args --remote-debugging-port=3434 usually does not enable CDP because Electron reuses the existing app instance. Restart Codex.app with the port enabled.
Fallback only when a separate instance cannot be used: restart all Codex.app processes and launch the binary with nohup.
pkill -TERM -f "/Applications/Codex.app" 2>/dev/null || true
sleep 2
if pgrep -f "/Applications/Codex.app" >/dev/null 2>&1; then
pkill -KILL -f "/Applications/Codex.app" 2>/dev/null || true
sleep 1
fi
nohup "/Applications/Codex.app/Contents/MacOS/Codex" \
--remote-debugging-port="$CDP_PORT" \
>/tmp/codex-cdp.log 2>&1 &
Pick the page target from /json/list where type == "page" and url starts with app://-/index.html. For Playwright screenshots, prefer chromium.connectOverCDP("http://127.0.0.1:$CDP_PORT"), select that page, wait briefly for React/app-server hydration, and save the screenshot.
Important caveats:
- Reuse any already-running Codex.app CDP endpoint when possible; do not spawn a second or third debug instance just because the default example uses
3434.
open -na "Codex" is required for a true separate instance; open -a "Codex" reuses an existing app process and often does not enable CDP flags.
- Always pass an isolated
--user-data-dir for the debug instance to avoid profile lock contention and cross-session side effects.
- If launched via raw binary, use
nohup or a long-lived shell; short one-shot launches can drop the CDP listener when the shell exits.
- Do not call
browser.close() when the Codex.app session should remain open.
- In Playwright builds where
browser.disconnect() is unavailable for CDP sessions, connect, inspect/capture, and exit the test process without close(); this preserves the running Codex.app instance.
- Existing helper processes can keep stale non-CDP state alive; killing all
/Applications/Codex.app processes is more reliable than only pkill -x Codex.
- CDP inspection can expose local thread titles and workspace names. Avoid pasting sensitive screenshot contents into public artifacts.
Findings: CDP Instance Reuse (2026-04-26)
- In this workspace, parity work often happens repeatedly in the same session, so a previously launched Codex.app debug instance may already be listening on a local CDP port.
- Before using
open -na "Codex" or starting a fresh debug profile, probe common local ports and reuse an existing endpoint when it already serves a valid app://-/index.html page target.
- Creating unnecessary extra Codex.app instances makes parity work noisier and can leave behind multiple stale debug profiles under
/tmp/codex-cdp-*.
Findings: External Electron Debug Launcher (2026-05-06)
- In this workspace, the most reliable parity-debug launch path is now:
bash /Users/igor/Git-projects/codex-web-local/scripts/run-codex-unpacked-debug.sh
- The helper intentionally uses external Electron instead of
/Applications/Codex.app/Contents/MacOS/Codex, because that preserves the generic Electron-style process/icon behavior some parity workflows expect while still launching the installed Codex app.asar.
- Using an unpinned external Electron such as
pnpm dlx electron can break startup because Codex.app expects Electron-41-era native resources; the current helper pins the runtime to electron@41.2.0.
- External-Electron startup also needs Codex’s bundled Sparkle native addon available at the external Electron resource path. The helper now prepares a shim by linking:
/Applications/Codex.app/Contents/Resources/native/sparkle.node
- into the matching
pnpm dlx Electron bundle before launch.
- Verified-good external debug state from this environment:
- browser/CDP endpoint exposed from
--remote-debugging-port
- Node inspector endpoint exposed from
--inspect
- WebSocket connection to the Node inspector target succeeds, not just
json/list
- When validating a parity session, do not stop at
curl /json/list; also confirm a real WebSocket connect to the returned webSocketDebuggerUrl.
Architecture Notes
- Renderer → Main Process: The renderer uses a
Uu HTTP client class that sends fetch-request IPC messages to the main process. The main process class tle handles these, adds auth tokens, and uses electron.net.fetch to make actual HTTP calls.
- Auth: Auth tokens come from the app-server's
getAuthStatus RPC method (ChatGPT backend auth).
- App-server: A
codex app-server child process communicating via JSON-RPC over stdin/stdout. Our bridge middleware proxies RPC calls to it.
- Config constants:
R7 = prodApiBaseUrl (https://chatgpt.com/backend-api), I7 = devApiBaseUrl (http://localhost:8000/api), C7 = originator (Codex Desktop).
Required Workflow (Feature Work)
- Identify target behavior:
- Restate what behavior is being added/changed.
- Define whether it is: data mapping, runtime event handling, UX text, visual treatment, interaction model, or all of these.
- Inspect Codex.app before coding:
- Locate the implementation in
app.asar (extract and search built assets as needed).
- Find relevant strings/keys/functions/components for the feature (status labels, event names, item types, summaries, collapse/expand behavior, etc.).
- Capture the closest equivalent pattern if exact parity is not present.
- Connect to the live Codex.app frontend over CDP and capture a screenshot of the target UI or closest equivalent before coding.
- Capture the current web UI before screenshot and list concrete gaps versus Codex.app.
- Build a parity checklist from Codex.app:
- Data model shape (fields used by UI).
- Realtime event sources and transitions.
- Rendering structure (what is shown collapsed vs expanded).
- Copy/text behavior (phrasing and status wording).
- Interaction behavior (auto-expand, auto-collapse, click/keyboard handling).
- Visibility rules (when elements appear/disappear).
- Implement against that checklist:
- Prefer Codex.app behavior over novel design.
- Keep deviations minimal and intentional.
- If deviating, include a short reason in the final response.
- Verify parity after implementation:
- Confirm each checklist item.
- Run local build/tests.
- Re-check UI behavior against Codex.app reference.
- Compare the implemented web UI screenshot against the Codex.app CDP reference screenshot.
- Iterate on fixable mismatches, then recapture the web UI after screenshot.
Response Requirements (When delivering feature changes)
For feature tasks, include:
Codex.app analysis: what was inspected (files/areas/patterns).
Codex.app CDP evidence: target URL/title, screenshot path, and visual behavior confirmed.
Before/after comparison: screenshot paths, gap list, and fix iteration result.
Parity result: matched items and any explicit deviations.
Fallback note only if Codex.app could not be inspected or had no equivalent.
Fallback Rules
If Codex.app cannot be inspected (missing app, extraction/search failure) or has no equivalent pattern:
- State the blocker explicitly.
- Use best local implementation consistent with existing repository patterns.
- Keep behavior conservative and avoid speculative UX innovations.
Scope and Safety
- This policy applies to feature behavior and UX decisions, not just styling.
- Bug fixes should still check Codex.app when they affect user-visible behavior.
- Prefer minimal patches that align with app behavior rather than large refactors.
Completion Verification Requirement
- After completing a task that changes behavior or UI, always run a Playwright verification in headless mode.
- Always capture a screenshot of the changed web result and display that screenshot in chat when reporting completion.
- Also keep the Codex.app CDP reference screenshot path in the completion report for user-visible feature/fix work.
- Include web-before and web-after screenshot paths, plus a short comparison result.
Self-Improvement Protocol
After each feature implementation session that uses this skill:
- Record new findings: Append a dated
## Findings: section documenting any newly discovered Codex.app internals (state keys, API endpoints, component patterns, auth flows, etc.).
- Update search instructions: If new search techniques were used (e.g., a better way to extract minified code, new file locations), update the "How to Search for Features" section.
- Update architecture notes: If new IPC channels, API endpoints, or data flows were discovered, add them to the Architecture Notes.
- Keep findings actionable: Each finding should include enough detail that a future session can reuse it without re-discovering.
Findings: Workspace Root Ordering (2026-02-25)
- Codex.app persists workspace root ordering/labels in global state JSON keys:
electron-saved-workspace-roots (order source of truth)
electron-workspace-root-labels
active-workspace-roots
- In this environment, persisted file path is:
~/.codex/.codex-global-state.json
- In packaged desktop runs, equivalent userData path is typically:
~/Library/Application Support/Codex/.codex-global-state.json
- For folder/project reorder parity, prefer reading these keys over browser LocalStorage-only ordering.
- Validation requirement for reorder changes:
- Run build/typecheck.
- Run Playwright in headless mode and capture a screenshot showing sidebar order.
Findings: Approval Request Payload Compatibility (2026-04-07)
- This workspace bundles app-server schemas that still expose JSON-RPC server request methods such as
item/commandExecution/requestApproval and item/fileChange/requestApproval, but the generated event typings also include newer approval event names such as exec_approval_request and apply_patch_approval_request.
- Newer approval payloads may carry snake_case fields (
turn_id, call_id, grant_root) or camelCase fields (conversationId, callId, grantRoot) instead of the older threadId / itemId request metadata.
- For CodexUI parity work involving approvals, normalize both method aliases and payload field aliases before rendering the pending-request UI; otherwise valid approval requests can fall through to the generic unknown-request actions.
- Live schema generated from
codex-cli 0.118.0 also includes JSON-RPC server requests for mcpServer/elicitation/request and item/permissions/requestApproval. The checked-in schema snapshot in this repo can lag behind the installed CLI, so for approval/request UI bugs it is worth generating fresh schemas locally via codex app-server generate-json-schema --out <dir> before deciding the app-server contract.
- In live MCP elicitation schemas, required fields without defaults should remain unset until the user provides a value; preselecting
false or the first enum option changes the meaning of the user’s response.
- For MCP
url elicitation mode, treat the server-provided URL as untrusted input and only render clickable links for safe schemes such as http: and https:.
Findings: Pinned Thread Persistence (2026-04-07)
- This workspace now persists pinned sidebar threads in Codex global state (
~/.codex/.codex-global-state.json) under key thread-pinned-ids.
- Bridge API endpoints added for web/client parity wiring:
GET /codex-api/thread-pins -> { data: { threadIds: string[] } }
PUT /codex-api/thread-pins with body { threadIds: string[] }
- Frontend behavior:
- Sidebar bootstraps pins from
thread-pinned-ids via the bridge endpoint.
- No
localStorage persistence is used for pinned-thread state.
Findings: Context Usage Meter (2026-04-01)
- Official
openai/codex app-server protocol exposes per-thread context telemetry via thread/tokenUsage/updated with:
tokenUsage.total
tokenUsage.last
tokenUsage.modelContextWindow
- In the official TUI, context-window percentage is derived from
last_token_usage, not cumulative total_token_usage.
- Official normalization subtracts a fixed
BASELINE_TOKENS = 12000 before computing remaining context percentage, so early turns do not look artificially "used".
- Official status/context copy found in the TUI favors:
- When docs are blocked, the quickest parity trace for this feature is:
codex-rs/app-server-protocol/schema/typescript/v2/ThreadTokenUsage*.ts
codex-rs/tui/src/chatwidget.rs
codex-rs/protocol/src/protocol.rs
Findings: File Change Turn Summaries (2026-03-30)
- Official app-server docs in
openai/codex confirm that:
turn/diff/updated carries { threadId, turnId, diff } as the latest aggregated unified diff for the whole turn.
fileChange thread items carry { id, changes, status }.
- Each
changes entry is { path, kind, diff }.
- For persisted/history-backed UI summaries, prefer
fileChange thread items over reconstructing state from deltas:
kind maps to add/delete/update.
update may include move_path for rename/move handling.
item/completed is the authoritative final state for whether edits actually applied.
- For user-facing file summaries, treat
turn/diff/updated as a supplemental aggregated diff source, not the only source:
- pure rename/move flows may not emit a meaningful turn diff payload for summary text.
fileChange items are the more reliable source for per-file operation labels.
Findings: Mobile Composer Submit Stabilization (2026-03-28)
- In this workspace, mobile web send UX is more reliable when submit does two things together:
Findings: Header Branch Switcher Includes Review Action (2026-04-08)
- Codex.app locale bundle includes explicit branch-search copy (
codex.composer.searchBranches), which aligns with searchable branch selection controls in header/composer surfaces.
- For parity in this repo, header-level branch control now combines:
- current branch display,
- branch switching via searchable dropdown,
- review-pane toggle action inside the same menu instead of a separate header button.
- Detached HEAD should be represented explicitly in the dropdown trigger when no branch name is available.
- blur the composer textarea immediately so the virtual keyboard dismisses
- trigger the conversation
jumpToLatest() immediately and again over the next animation frames so the viewport stays pinned after the keyboard resize
- Relying on conversation auto-follow alone is not enough for the mobile keyboard-close transition because the viewport height change can land after the first bottom-lock pass.
Findings: Thread Forking (2026-03-28)
- The bundled app-server protocol in this repo already exposes stable
thread/fork support in v2, so UI work should call the RPC directly instead of simulating a new thread locally.
ThreadForkParams.path is documented as an unstable rollout-path override, while threadId remains the preferred stable entry point for IDE clients.
- When implementing “fork from this answer” in the UI, a safe repo-local strategy is:
- call
thread/fork for the source thread
- then call
thread/rollback on the new thread for trailing turns after the chosen answer
- Verification can assert real branching, not just button presence:
- fork from a non-final response in Playwright
- confirm URL changes to a new thread id
- confirm the new thread has fewer turns than the source thread
- Thread title rendering in this fork must prefer server-provided
name/title over preview; otherwise renamed forked threads will still look identical to the source thread in the header and sidebar.
Findings: Ordered List Numbering (2026-03-27)
ThreadConversation.vue uses a custom Markdown block parser rather than a standard Markdown library.
- Ordered-list items separated by non-indented paragraphs are parsed into multiple
orderedList blocks.
- To preserve author-visible numbering in that case, each
orderedList block needs the original marker value persisted and rendered via the HTML <ol start=\"...\"> attribute.
Findings: Dictation / Microphone Feature (2026-02-26)
- i18n keys:
composer.dictation.* — tooltip is "Hold to dictate", aria is "Dictate".
- Component:
M4n React hook handles recording state, audio capture, and transcription.
- Audio pipeline:
navigator.mediaDevices.getUserMedia({ audio: { channelCount: 1 } }) → MediaRecorder → chunks → Blob → multipart POST.
- Transcription endpoint: The renderer sends audio to
/transcribe via the IPC fetch proxy. The main process (tle class) prepends the prodApiBaseUrl (https://chatgpt.com/backend-api) and attaches ChatGPT auth bearer tokens. Full URL: https://chatgpt.com/backend-api/transcribe.
- Request format: Multipart form-data with boundary
----codex-transcribe-<uuid>, fields: file (audio blob) and optional language. Body is base64-encoded and sent with X-Codex-Base64: 1 header.
- Response:
{ text: "transcribed text" }.
- Interaction model: Press-and-hold to record → release to stop and transcribe → text inserted into composer. Has "insert" and "send" modes.
- Icon:
audiowave-dark.svg / audiowave-light.svg (custom SVG, not from icon library).
- Web app implementation: Our bridge proxies
/codex-api/transcribe to the ChatGPT backend using auth tokens from the app-server getAuthStatus RPC. Frontend uses useDictation composable with MediaRecorder API.
Findings: Chat Markdown Image Embeds (2026-03-04)
- Codex.app renderer bundle includes markdown-to-HTML image handling (
image({href,title,text}) emits <img src="...">), consistent with inline markdown image rendering in assistant/user text.
- In web parity mode, absolute local paths in markdown image URLs need explicit server mediation; browser runtime does not resolve
/Users/... as local files.
- A dedicated local image endpoint (
/codex-local-image?path=...) is required for parity-like rendering of absolute filesystem image paths in browser-delivered UI.
- Express
sendFile must allow dot-directory segments (dotfiles: 'allow') or paths under ~/.codex/... return 404 despite existing files.
Findings: Composer Enter Behavior (2026-03-05)
- Codex.app composer input is rich-text/multiline (
ProseMirror-based), not single-line.
- Enter handling is configurable (
enterBehavior):
enter submits by default.
newline inserts a newline on Enter.
cmdIfMultiline inserts newline when multiline, otherwise submits.
- Newline shortcuts are explicitly bound:
Shift-Enter inserts newline.
Alt-Enter inserts newline.
Mod-Enter submits.
- This confirms multiline composition parity requires newline-capable input plus explicit Enter-vs-newline key handling.
Findings: Composer @ Mentions (2026-03-05)
- Codex.app uses a dedicated mention trigger plugin for
@ with pattern /(^|\s)(@[^\s@]*)$/, so mentions activate at word boundaries and stop on whitespace or a second @.
- Mention entries are stored as an inline
mention-ui node with attrs { label, path, fsPath }, rendered with data attributes at-mention-label, at-mention-path, and at-mention-fs-path.
- Mention picker keyboard behavior includes:
Escape closes mention UI.
Enter and Tab commit the highlighted mention.
- Composer placeholder copy in local mode explicitly documents this affordance:
Ask Codex anything, @ to add files, / for commands.
Findings: Thread Rename Flow (2026-03-12)
- Codex.app locale keys confirm sidebar rename flow is dialog-based, not inline:
sidebarElectron.renameThread
sidebarElectron.renameThreadDialogTitle
sidebarElectron.renameThreadDialogSubtitle
sidebarElectron.renameThreadDialogPlaceholder
sidebarElectron.renameThreadDialogSave
sidebarElectron.renameThreadDialogCancel
sidebarElectron.renameThreadDialogAriaLabel
- App-server RPC for rename uses method
thread/name/set with params { threadId, name } (not threadName).
thread/name/updated realtime notification carries { threadId, threadName }, so parity implementations should handle both request/response naming differences (name on write, threadName on notification).
Findings: Local Parity Fallback (2026-03-27)
- In this workspace,
/Applications/Codex.app/Contents/Resources/app.asar was not present, so Codex.app-first inspection could not run.
- For user-visible changes under this constraint, use the skill's fallback path explicitly: preserve existing repository interaction patterns, keep the UX conservative, and call out the parity blocker in the completion report.
Findings: Settings Account Labels (2026-03-24)
- No equivalent multi-account switcher UI was found in the installed Codex.app bundle for Settings/account list behavior, so parity work should follow the existing local Settings visual language instead of inventing a separate header menu.
- When showing multiple saved accounts that share the same email, account/workspace identity needs its own dedicated label line; folding the workspace identifier into secondary metadata and truncating it makes entries indistinguishable in narrow sidebars.
Findings: Mobile Install Icons (2026-03-23)
- In this web workspace, mobile home-screen installation depends on both
link[rel="apple-touch-icon"] in index.html and the PNG entries in public/manifest.webmanifest; updating only the manifest is not enough for iPhone-style add-to-home-screen flows.
- Small PNGs generated from SVG via headless Chrome can silently degrade to all-white images when the SVG is loaded indirectly during screenshot capture. Rendering a reliable large PNG first and deriving smaller sizes from that output avoids blank icon assets.
- For non-transparent exported PNG icons, any transparent margin around the SVG is rasterized against the page background. Set an explicit dark page background during capture or use full-bleed icon artwork to avoid white edges that make installed icons look washed out.
- A more reliable fix than screenshot capture is to rasterize SVGs through a browser canvas (
Image + drawImage + canvas.toDataURL()), which preserves the real SVG bounds and avoids dark corner contamination from the page background.
Findings: PWA Packaging Fallback (2026-03-23)
- This repository can be made installable as a browser app without changing runtime behavior by adding standard PWA assets:
- HTML manifest link + theme color metadata
- production-only service worker registration in
src/main.ts
- static
manifest.webmanifest
- static icons under
public/icons/
- Codex.app desktop parity could not be inspected in this environment because
/Applications/Codex.app was unavailable, so PWA packaging should follow the fallback path and avoid speculative UX changes.
- A conservative service worker strategy for this repo is:
- bypass
/codex-api/* and local file proxy endpoints
- use navigation fallback to cached
/
- use runtime caching for same-origin static assets and manifest
Findings: Plan Mode Turn Start (2026-03-22)
- App-server rejects
turn/start.collaborationMode unless the client advertises initialize.capabilities.experimentalApi = true.
turn/start.collaborationMode.settings.model must be a non-empty concrete model id. Sending "" can leave a plan-mode thread stuck or fail without rendering plan output.
- In this environment,
collaborationMode/list returns Plan with mode: "plan" and reasoning_effort: "medium", but model is null, so the client must source the actual model from current config or available models before starting the turn.
Findings: Account Rate Limits Protocol (2026-03-21)
- App-server exposes quota state via
account/rateLimits/read and pushes live updates with account/rateLimits/updated.
- Read responses can include
rateLimitsByLimitId.codex; if absent, fall back to the legacy top-level rateLimits bucket.
- Runtime payloads use camelCase fields:
- snapshot:
limitId, limitName, planType, primary, secondary, credits
- window:
usedPercent, windowDurationMins, resetsAt
- credits:
hasCredits, unlimited, balance
- For compact composer display, a conservative summary can be derived from
primary/secondary windows without forcing a full account panel.
- Weekly refresh copy can be derived entirely client-side by selecting the quota window whose
windowDurationMins is 10080 (or the nearest longer weekly-like window) and formatting its resetsAt timestamp into a calendar date for the tooltip.
- On touch/mobile surfaces, quota details hidden only in a
title attribute are effectively invisible. Weekly refresh information needs to be rendered as visible text in the composer quota badge, not only in hover-only tooltip content.
- For compact inline display, the weekly refresh segment should be date-only (for example
Mar 28 / 3月28日) and appended on the same line as the quota summary instead of using a separate explanatory label.
Findings: Empty Project Removal Persistence (2026-03-21)
- In this web UI, empty project groups can be recreated purely from persisted workspace-root state, even when no threads exist for that project.
Findings: Mobile Foreground Resume (2026-03-23)
- In this web workspace, a conservative mobile-only freshness policy can be implemented at the app shell level (
App.vue) by combining:
document.visibilitychange to record background entry
window.pageshow to catch BFCache restores
window.focus as a final foreground fallback
- Restricting the behavior to the existing
<768px mobile breakpoint avoids forcing reloads on tablet/desktop layouts during tab focus changes.
- A small hidden-duration threshold helps avoid accidental reloads from transient overlays while still reloading after real app switches.
- The persistence source of truth is still the global state keys:
electron-saved-workspace-roots
electron-workspace-root-labels
active-workspace-roots
- Removing a project must delete matching workspace-root entries from all three persisted collections; updating in-memory order alone is insufficient because hydration will rebuild an empty placeholder group on refresh.
- The placeholder group is produced by
orderGroupsByProjectOrder(...), which materializes { projectName, threads: [] } when a persisted project name has no matching incoming thread group.
Findings: Markdown Block Rendering Fallback (2026-03-21)
- Codex.app could not be inspected in this Linux environment because
/Applications/Codex.app is unavailable.
- Conservative fallback for message markdown rendering is to keep the existing inline parser and add a lightweight block parser in
ThreadConversation.vue.
- A low-risk split that matches existing web UI structure is:
- block-level parsing for paragraphs, unordered lists, ordered lists, and inline markdown images
- inline parsing reused for bold, inline code, URLs, and file links
- This avoids introducing a full markdown dependency while fixing the most visible raw-markup regressions (
- item, 1. item, **bold**).
- Expanded fallback block support that still fits this local parser architecture:
- headings (
# ... ######)
- blockquotes (
> quote)
- task lists (
- [ ], - [x])
- thematic breaks (
---, ***, ___)
- fenced code blocks (``` / ~~~)
- To avoid breaking local-image rendering and file-link handling, code-fence splitting should happen before inline image token splitting, otherwise
 inside fenced code can be misparsed as a real image block.
Findings: Mobile Composer Auto-Zoom Fallback (2026-03-21)
- Codex.app could not be inspected in this environment, so mobile zoom behavior was handled with a browser-level fallback.
Findings: Plan Mode Fallback Wiring (2026-03-22)
- Codex.app could not be inspected in this Linux environment because
/Applications/Codex.app is unavailable, so plan-mode behavior was aligned to the shipped app-server protocol instead of renderer-bundle parity.
- App-server protocol already exposes the full plan-mode surface needed by the web UI:
TurnStartParams.collaborationMode
ModeKind = "plan" | "default"
turn/plan/updated
item/plan/delta
ThreadItem.type = "plan"
item/tool/requestUserInput
- Conservative web fallback for plan mode:
- persist the selected collaboration mode locally under
codex-web-local.collaboration-mode.v1
- send
collaborationMode: { mode: "plan", settings: { model, reasoning_effort, developer_instructions: null } } only when plan mode is selected
- omit
collaborationMode entirely for default mode to disable plan mode cleanly
collaborationMode/list can be treated as advisory rather than authoritative for web fallback:
- when available, use server labels for
default / plan
- still keep static
Default and Plan options available so the feature remains usable against servers that lag the preset-list endpoint
request_user_input questions need broader handling than the original approval-like UI:
- support selectable options with descriptions
- support free-text questions when
options is null or empty
- support secret answers via password inputs when
isSecret is true
- On mobile browsers, especially iOS Safari, focusing text inputs below
16px commonly triggers viewport auto-zoom.
- In this repo, the main composer textarea used
text-sm (14px computed on mobile), which is sufficient to trigger that browser behavior.
- Conservative fix: keep viewport meta unchanged and raise focusable text input font-size to
16px on mobile widths, instead of disabling pinch zoom globally.
Findings: Dark Markdown Theme Coverage Fallback (2026-03-21)
- Codex.app could not be inspected in this environment, so dark-mode markdown behavior was aligned using existing web theme conventions.
- When adding new markdown block renderers in
ThreadConversation.vue, matching :root.dark overrides must also be added in style.css; otherwise the new nodes inherit light-theme slate colors and become low-contrast in dark mode.
- The markdown-specific classes that require explicit dark coverage in the current UI are:
- headings (
.message-heading, .message-heading-h6)
- emphasis (
.message-bold-text, .message-italic-text, .message-strikethrough-text)
- blockquote/list/task styles (
.message-blockquote, .message-list, .message-task-checkbox)
- fenced code and divider styles (
.message-code-block, .message-code-language, .message-divider)
- A safe fallback pattern is to keep foreground text near existing dark message colors (
zinc-100/zinc-200) and move structural surfaces to darker zinc backgrounds, so markdown blocks remain legible without deviating from the current dark theme.
Findings: Web Title Branding Fallback (2026-03-21)
- Codex.app could not be inspected in this environment, so title branding was aligned using the existing web entry points.
- The browser tab title for the
…(truncated)
1---2name: codex-app-parity3description: Use only when the user explicitly mentions Codex parity, codex-app-parity, Codex.app parity, or asks to compare against the installed Codex desktop app.4---56# Codex App Parity Skill78Use this skill only when the user explicitly asks for Codex parity work, names `codex-app-parity`, mentions Codex.app parity, or asks to compare behavior against the installed Codex desktop app.9Do not auto-trigger this skill for ordinary feature work, UI changes, or user-visible behavior changes unless the user explicitly requests that parity workflow.1011## Objective1213Ensure behavior is implemented with Codex.app as the source of truth, then verified with headless Playwright and screenshots.1415## Project Instructions1617## Repo Knowledge Maintenance1819For user-visible Directory, Skills, Apps, Plugins, MCP, or Composio changes in this repo:2021- Update the relevant manual test doc under `tests/<domain>/...` with verification steps, including light and dark theme checks. Update `tests.md` only when adding, renaming, or removing a domain folder.22- If the change creates or changes durable behavior/architecture, add or update an `llm-wiki/raw/...` source and corresponding `llm-wiki/wiki/...` concept page.23- Keep `whatToTest.md` as a short pending-only checklist; remove items that were actually executed successfully.24- Prefer assertions plus screenshots for browser validation; screenshots alone are not enough.2526## Codex.app-First Development Policy2728For every **new feature** and every **behavior/UI change**, treat the installed desktop app as the source of truth:2930- App path: `/Applications/Codex.app`31- Primary bundle to inspect: `/Applications/Codex.app/Contents/Resources/app.asar`3233Do not implement first and compare later. Compare first, then implement.3435## How to Search for Features in Codex.app3637### Extraction3839Extract the app bundle once (reuse if already extracted):4041```bash42mkdir -p /tmp/codex-app-extracted43npx asar extract "/Applications/Codex.app/Contents/Resources/app.asar" /tmp/codex-app-extracted44```4546### Key Directories4748| Directory | Contents |49|-----------|----------|50| `/tmp/codex-app-extracted/webview/assets/` | Main frontend bundle (`index-*.js`) + locale files |51| `/tmp/codex-app-extracted/.vite/build/` | Electron main process (`main.js`, `main-*.js`, `preload.js`, `worker.js`) |52| `/tmp/codex-app-extracted/package.json` | App metadata, version, entry point |5354### Searching the Minified Bundle5556The main UI bundle is a single large minified JS file at `webview/assets/index-*.js`. Use Python to search since `grep -o` with large repeat counts fails on macOS:5758```python59python3 -c "60with open('/tmp/codex-app-extracted/webview/assets/index-<hash>.js', 'r') as f:61 content = f.read()62idx = content.find('YOUR_SEARCH_TERM')63if idx >= 0:64 print(content[max(0, idx-200):idx+500])65"66```6768### What to Search For69701. **i18n keys**: Search locale files (`webview/assets/zh-TW-*.js`, `webview/assets/en-*.js`, etc.) for human-readable labels. Keys follow the pattern `component.feature.property` (e.g., `composer.dictation.tooltip`).71722. **Component functions**: Minified React components follow patterns like `function X4n({prop1:t,prop2:e,...})`. Search for the feature's i18n key to find the component that renders it.73743. **API calls and endpoints**: Search main process files (`.vite/build/main-*.js`) for endpoint URLs, auth handling, and IPC channels. Key patterns:75 - `prodApiBaseUrl` → production API base (e.g., `https://chatgpt.com/backend-api`)76 - `devApiBaseUrl` → dev API base (e.g., `http://localhost:8000/api`)77 - `fetch-request` / `fetch-response` → IPC-proxied HTTP calls from renderer to main process78794. **Icon names**: Search for icon imports like `audiowave-dark.svg`, `book-open-dark.svg`. Icon mapping is in the main bundle around the `Hwn=Object.assign({` pattern.80815. **Keyboard shortcuts**: Search for `CmdOrCtrl+`, `Cmd+`, `keydown`, `keyCode`, or specific key names.8283### Search Strategy84851. Start with **i18n locale files** — they have human-readable labels that identify features.862. Use the i18n key to find the **component** in the main bundle.873. Trace the component to find **hooks/composables**, **API calls**, and **event handlers**.884. Check the **main process** bundle for any server-side proxying or Electron IPC handling.8990## Mandatory CDP Frontend Inspection9192For every feature UI or user-visible fix, inspect the live Codex.app frontend over Chrome DevTools Protocol before implementing. Bundle search is still useful, but it is not enough by itself when a visual/interaction surface exists.9394### Required CDP Evidence9596- Connect to Codex.app over CDP.97- Navigate or interact until the relevant feature UI, closest equivalent UI, or broken/fixed state is visible.98- Capture a screenshot under `output/playwright/` with a task-specific filename.99- Record in the final response:100 - CDP endpoint/port101 - Codex.app target URL/title102 - screenshot absolute path103 - what was visually confirmed104105If the exact UI cannot be reached, capture the closest relevant Codex.app surface and state the gap.106107## Mandatory Comparison and Fix Iteration108109For every feature UI or user-visible fix, compare Codex.app against the web UI **before and after implementation**.110111Required artifacts:112113- `codex-reference`: Codex.app CDP screenshot of the target feature UI or closest equivalent.114- `web-before`: current web UI screenshot before code changes, showing the existing gap or missing behavior.115- `web-after`: web UI screenshot after implementation, showing the proposed parity result.116117Required comparison notes:118119- Before coding, write a short parity gap list from `codex-reference` vs `web-before`.120- After coding, compare `web-after` against `codex-reference`.121- Classify every notable mismatch as:122 - `fixed`: matched or acceptably aligned123 - `intentional deviation`: documented reason124 - `needs follow-up`: not fixed in this task125- If `web-after` reveals a fixable mismatch in layout, copy, visibility, interaction, or state handling, do another implementation iteration and capture a new `web-after` screenshot.126- Do not report completion until the iteration has either resolved the mismatch or documented why it remains.127128Use task-specific screenshot names under `output/playwright/`, for example:129130- `output/playwright/<task>-codex-reference.png`131- `output/playwright/<task>-web-before.png`132- `output/playwright/<task>-web-after.png`133134### Reliable CDP Launch Pattern135136Before launching anything new, first check whether a Codex.app CDP endpoint is already available and reusable. Avoid creating additional Codex instances when an existing CDP-enabled instance already exposes a usable `app://-/index.html` page target.137138Preferred reuse check:139140```bash141for port in 3434 3435 9222 9223; do142 if curl -fsS "http://127.0.0.1:$port/json/list" >/tmp/codex-cdp-list.json 2>/dev/null; then143 python3 - <<'PY'144import json145from pathlib import Path146rows = json.loads(Path('/tmp/codex-cdp-list.json').read_text())147page = next((row for row in rows if row.get('type') == 'page' and str(row.get('url', '')).startswith('app://-/index.html')), None)148if page:149 print(page['webSocketDebuggerUrl'])150PY151 if [ -s /tmp/codex-cdp-list.json ]; then152 echo "Reusing CDP on port $port"153 break154 fi155 fi156done157```158159If a usable target is found, reuse it and do not launch another Codex instance.160161Only if no reusable CDP target exists, prefer running a separate Codex.app debug instance so the user's normal Codex session is not interrupted and the CDP target can stay alive after tests.162163In this repo, prefer the maintained helper script first:164165```bash166bash /Users/igor/Git-projects/codex-web-local/scripts/run-codex-unpacked-debug.sh167```168169The script:170171- launches Codex.app from the installed `app.asar` under external Electron172- pins the external runtime to `electron@41.2.0`173- auto-picks free CDP and Node inspector ports174- verifies the endpoints after launch175- prepares the required native Sparkle shim for external-Electron runs176177If the helper script fails, treat the failure as a skill maintenance signal, not just a one-off launch error:178179- Inspect the failing shell script and its nearby helper scripts before using a manual fallback.180- Fix durable launcher bugs in the `.sh` scripts when the cause is clear and local to the script.181- Re-run the helper after the fix and update this skill with any new reliable launch finding.182- Use a manual launch fallback only when the script cannot be repaired safely in the current task.183184Use `--verify-only` when you only need to confirm whether the current endpoints are still alive.185186Use a fresh app instance with its own profile directory:187188```bash189CDP_PORT=3434190while lsof -i :"$CDP_PORT" >/dev/null 2>&1; do191 CDP_PORT=$((CDP_PORT + 1))192done193194CDP_PROFILE_DIR="/tmp/codex-cdp-$CDP_PORT"195mkdir -p "$CDP_PROFILE_DIR"196197open -na "Codex" --args \198 --remote-debugging-port="$CDP_PORT" \199 --user-data-dir="$CDP_PROFILE_DIR"200201until curl -fsS "http://127.0.0.1:$CDP_PORT/json/list" >/tmp/codex-cdp-list.json; do202 sleep 1203done204```205206If Codex.app is already running without CDP, `open -a "Codex" --args --remote-debugging-port=3434` usually does **not** enable CDP because Electron reuses the existing app instance. Restart Codex.app with the port enabled.207Fallback only when a separate instance cannot be used: restart all Codex.app processes and launch the binary with `nohup`.208209```bash210pkill -TERM -f "/Applications/Codex.app" 2>/dev/null || true211sleep 2212if pgrep -f "/Applications/Codex.app" >/dev/null 2>&1; then213 pkill -KILL -f "/Applications/Codex.app" 2>/dev/null || true214 sleep 1215fi216217nohup "/Applications/Codex.app/Contents/MacOS/Codex" \218 --remote-debugging-port="$CDP_PORT" \219 >/tmp/codex-cdp.log 2>&1 &220```221222Pick the page target from `/json/list` where `type == "page"` and `url` starts with `app://-/index.html`. For Playwright screenshots, prefer `chromium.connectOverCDP("http://127.0.0.1:$CDP_PORT")`, select that page, wait briefly for React/app-server hydration, and save the screenshot.223224Important caveats:225226- Reuse any already-running Codex.app CDP endpoint when possible; do not spawn a second or third debug instance just because the default example uses `3434`.227- `open -na "Codex"` is required for a true separate instance; `open -a "Codex"` reuses an existing app process and often does not enable CDP flags.228- Always pass an isolated `--user-data-dir` for the debug instance to avoid profile lock contention and cross-session side effects.229- If launched via raw binary, use `nohup` or a long-lived shell; short one-shot launches can drop the CDP listener when the shell exits.230- Do not call `browser.close()` when the Codex.app session should remain open.231- In Playwright builds where `browser.disconnect()` is unavailable for CDP sessions, connect, inspect/capture, and exit the test process without `close()`; this preserves the running Codex.app instance.232- Existing helper processes can keep stale non-CDP state alive; killing all `/Applications/Codex.app` processes is more reliable than only `pkill -x Codex`.233- CDP inspection can expose local thread titles and workspace names. Avoid pasting sensitive screenshot contents into public artifacts.234235## Findings: CDP Instance Reuse (2026-04-26)236237- In this workspace, parity work often happens repeatedly in the same session, so a previously launched Codex.app debug instance may already be listening on a local CDP port.238- Before using `open -na "Codex"` or starting a fresh debug profile, probe common local ports and reuse an existing endpoint when it already serves a valid `app://-/index.html` page target.239- Creating unnecessary extra Codex.app instances makes parity work noisier and can leave behind multiple stale debug profiles under `/tmp/codex-cdp-*`.240241## Findings: External Electron Debug Launcher (2026-05-06)242243- In this workspace, the most reliable parity-debug launch path is now:244 - `bash /Users/igor/Git-projects/codex-web-local/scripts/run-codex-unpacked-debug.sh`245- The helper intentionally uses external Electron instead of `/Applications/Codex.app/Contents/MacOS/Codex`, because that preserves the generic Electron-style process/icon behavior some parity workflows expect while still launching the installed Codex `app.asar`.246- Using an unpinned external Electron such as `pnpm dlx electron` can break startup because Codex.app expects Electron-41-era native resources; the current helper pins the runtime to `electron@41.2.0`.247- External-Electron startup also needs Codex’s bundled Sparkle native addon available at the external Electron resource path. The helper now prepares a shim by linking:248 - `/Applications/Codex.app/Contents/Resources/native/sparkle.node`249 - into the matching `pnpm dlx` Electron bundle before launch.250- Verified-good external debug state from this environment:251 - browser/CDP endpoint exposed from `--remote-debugging-port`252 - Node inspector endpoint exposed from `--inspect`253 - WebSocket connection to the Node inspector target succeeds, not just `json/list`254- When validating a parity session, do not stop at `curl /json/list`; also confirm a real WebSocket connect to the returned `webSocketDebuggerUrl`.255256### Architecture Notes257258- **Renderer → Main Process**: The renderer uses a `Uu` HTTP client class that sends `fetch-request` IPC messages to the main process. The main process class `tle` handles these, adds auth tokens, and uses `electron.net.fetch` to make actual HTTP calls.259- **Auth**: Auth tokens come from the app-server's `getAuthStatus` RPC method (ChatGPT backend auth).260- **App-server**: A `codex app-server` child process communicating via JSON-RPC over stdin/stdout. Our bridge middleware proxies RPC calls to it.261- **Config constants**: `R7` = prodApiBaseUrl (`https://chatgpt.com/backend-api`), `I7` = devApiBaseUrl (`http://localhost:8000/api`), `C7` = originator (`Codex Desktop`).262263## Required Workflow (Feature Work)2642651. Identify target behavior:266- Restate what behavior is being added/changed.267- Define whether it is: data mapping, runtime event handling, UX text, visual treatment, interaction model, or all of these.2682692. Inspect Codex.app before coding:270- Locate the implementation in `app.asar` (extract and search built assets as needed).271- Find relevant strings/keys/functions/components for the feature (status labels, event names, item types, summaries, collapse/expand behavior, etc.).272- Capture the closest equivalent pattern if exact parity is not present.273- Connect to the live Codex.app frontend over CDP and capture a screenshot of the target UI or closest equivalent before coding.274- Capture the current web UI before screenshot and list concrete gaps versus Codex.app.2752763. Build a parity checklist from Codex.app:277- Data model shape (fields used by UI).278- Realtime event sources and transitions.279- Rendering structure (what is shown collapsed vs expanded).280- Copy/text behavior (phrasing and status wording).281- Interaction behavior (auto-expand, auto-collapse, click/keyboard handling).282- Visibility rules (when elements appear/disappear).2832844. Implement against that checklist:285- Prefer Codex.app behavior over novel design.286- Keep deviations minimal and intentional.287- If deviating, include a short reason in the final response.2882895. Verify parity after implementation:290- Confirm each checklist item.291- Run local build/tests.292- Re-check UI behavior against Codex.app reference.293- Compare the implemented web UI screenshot against the Codex.app CDP reference screenshot.294- Iterate on fixable mismatches, then recapture the web UI after screenshot.295296## Response Requirements (When delivering feature changes)297298For feature tasks, include:299300- `Codex.app analysis`: what was inspected (files/areas/patterns).301- `Codex.app CDP evidence`: target URL/title, screenshot path, and visual behavior confirmed.302- `Before/after comparison`: screenshot paths, gap list, and fix iteration result.303- `Parity result`: matched items and any explicit deviations.304- `Fallback note` only if Codex.app could not be inspected or had no equivalent.305306## Fallback Rules307308If Codex.app cannot be inspected (missing app, extraction/search failure) or has no equivalent pattern:309310- State the blocker explicitly.311- Use best local implementation consistent with existing repository patterns.312- Keep behavior conservative and avoid speculative UX innovations.313314## Scope and Safety315316- This policy applies to **feature behavior and UX decisions**, not just styling.317- Bug fixes should still check Codex.app when they affect user-visible behavior.318- Prefer minimal patches that align with app behavior rather than large refactors.319320## Completion Verification Requirement321322- After completing a task that changes behavior or UI, always run a Playwright verification in **headless** mode.323- Always capture a screenshot of the changed web result and display that screenshot in chat when reporting completion.324- Also keep the Codex.app CDP reference screenshot path in the completion report for user-visible feature/fix work.325- Include web-before and web-after screenshot paths, plus a short comparison result.326327## Self-Improvement Protocol328329After each feature implementation session that uses this skill:3303311. **Record new findings**: Append a dated `## Findings:` section documenting any newly discovered Codex.app internals (state keys, API endpoints, component patterns, auth flows, etc.).3322. **Update search instructions**: If new search techniques were used (e.g., a better way to extract minified code, new file locations), update the "How to Search for Features" section.3333. **Update architecture notes**: If new IPC channels, API endpoints, or data flows were discovered, add them to the Architecture Notes.3344. **Keep findings actionable**: Each finding should include enough detail that a future session can reuse it without re-discovering.335336## Findings: Workspace Root Ordering (2026-02-25)337338- Codex.app persists workspace root ordering/labels in global state JSON keys:339 - `electron-saved-workspace-roots` (order source of truth)340 - `electron-workspace-root-labels`341 - `active-workspace-roots`342- In this environment, persisted file path is:343 - `~/.codex/.codex-global-state.json`344- In packaged desktop runs, equivalent userData path is typically:345 - `~/Library/Application Support/Codex/.codex-global-state.json`346- For folder/project reorder parity, prefer reading these keys over browser LocalStorage-only ordering.347- Validation requirement for reorder changes:348 - Run build/typecheck.349 - Run Playwright in headless mode and capture a screenshot showing sidebar order.350351## Findings: Approval Request Payload Compatibility (2026-04-07)352353- This workspace bundles app-server schemas that still expose JSON-RPC server request methods such as `item/commandExecution/requestApproval` and `item/fileChange/requestApproval`, but the generated event typings also include newer approval event names such as `exec_approval_request` and `apply_patch_approval_request`.354- Newer approval payloads may carry snake_case fields (`turn_id`, `call_id`, `grant_root`) or camelCase fields (`conversationId`, `callId`, `grantRoot`) instead of the older `threadId` / `itemId` request metadata.355- For CodexUI parity work involving approvals, normalize both method aliases and payload field aliases before rendering the pending-request UI; otherwise valid approval requests can fall through to the generic unknown-request actions.356- Live schema generated from `codex-cli 0.118.0` also includes JSON-RPC server requests for `mcpServer/elicitation/request` and `item/permissions/requestApproval`. The checked-in schema snapshot in this repo can lag behind the installed CLI, so for approval/request UI bugs it is worth generating fresh schemas locally via `codex app-server generate-json-schema --out <dir>` before deciding the app-server contract.357- In live MCP elicitation schemas, required fields without defaults should remain unset until the user provides a value; preselecting `false` or the first enum option changes the meaning of the user’s response.358- For MCP `url` elicitation mode, treat the server-provided URL as untrusted input and only render clickable links for safe schemes such as `http:` and `https:`.359360## Findings: Pinned Thread Persistence (2026-04-07)361362- This workspace now persists pinned sidebar threads in Codex global state (`~/.codex/.codex-global-state.json`) under key `thread-pinned-ids`.363- Bridge API endpoints added for web/client parity wiring:364 - `GET /codex-api/thread-pins` -> `{ data: { threadIds: string[] } }`365 - `PUT /codex-api/thread-pins` with body `{ threadIds: string[] }`366- Frontend behavior:367 - Sidebar bootstraps pins from `thread-pinned-ids` via the bridge endpoint.368 - No `localStorage` persistence is used for pinned-thread state.369370## Findings: Context Usage Meter (2026-04-01)371372- Official `openai/codex` app-server protocol exposes per-thread context telemetry via `thread/tokenUsage/updated` with:373 - `tokenUsage.total`374 - `tokenUsage.last`375 - `tokenUsage.modelContextWindow`376- In the official TUI, context-window percentage is derived from `last_token_usage`, not cumulative `total_token_usage`.377- Official normalization subtracts a fixed `BASELINE_TOKENS = 12000` before computing remaining context percentage, so early turns do not look artificially "used".378- Official status/context copy found in the TUI favors:379 - `X% left`380 - `Y used`381 - `Z window`382- When docs are blocked, the quickest parity trace for this feature is:383 - `codex-rs/app-server-protocol/schema/typescript/v2/ThreadTokenUsage*.ts`384 - `codex-rs/tui/src/chatwidget.rs`385 - `codex-rs/protocol/src/protocol.rs`386387## Findings: File Change Turn Summaries (2026-03-30)388389- Official app-server docs in `openai/codex` confirm that:390 - `turn/diff/updated` carries `{ threadId, turnId, diff }` as the latest aggregated unified diff for the whole turn.391 - `fileChange` thread items carry `{ id, changes, status }`.392 - Each `changes` entry is `{ path, kind, diff }`.393- For persisted/history-backed UI summaries, prefer `fileChange` thread items over reconstructing state from deltas:394 - `kind` maps to add/delete/update.395 - `update` may include `move_path` for rename/move handling.396 - `item/completed` is the authoritative final state for whether edits actually applied.397- For user-facing file summaries, treat `turn/diff/updated` as a supplemental aggregated diff source, not the only source:398 - pure rename/move flows may not emit a meaningful turn diff payload for summary text.399 - `fileChange` items are the more reliable source for per-file operation labels.400401## Findings: Mobile Composer Submit Stabilization (2026-03-28)402403- In this workspace, mobile web send UX is more reliable when submit does two things together:404405## Findings: Header Branch Switcher Includes Review Action (2026-04-08)406407- Codex.app locale bundle includes explicit branch-search copy (`codex.composer.searchBranches`), which aligns with searchable branch selection controls in header/composer surfaces.408- For parity in this repo, header-level branch control now combines:409 - current branch display,410 - branch switching via searchable dropdown,411 - review-pane toggle action inside the same menu instead of a separate header button.412- Detached HEAD should be represented explicitly in the dropdown trigger when no branch name is available.413 - blur the composer textarea immediately so the virtual keyboard dismisses414 - trigger the conversation `jumpToLatest()` immediately and again over the next animation frames so the viewport stays pinned after the keyboard resize415- Relying on conversation auto-follow alone is not enough for the mobile keyboard-close transition because the viewport height change can land after the first bottom-lock pass.416417## Findings: Thread Forking (2026-03-28)418419- The bundled app-server protocol in this repo already exposes stable `thread/fork` support in v2, so UI work should call the RPC directly instead of simulating a new thread locally.420- `ThreadForkParams.path` is documented as an unstable rollout-path override, while `threadId` remains the preferred stable entry point for IDE clients.421- When implementing “fork from this answer” in the UI, a safe repo-local strategy is:422 - call `thread/fork` for the source thread423 - then call `thread/rollback` on the new thread for trailing turns after the chosen answer424- Verification can assert real branching, not just button presence:425 - fork from a non-final response in Playwright426 - confirm URL changes to a new thread id427 - confirm the new thread has fewer turns than the source thread428- Thread title rendering in this fork must prefer server-provided `name`/`title` over `preview`; otherwise renamed forked threads will still look identical to the source thread in the header and sidebar.429430## Findings: Ordered List Numbering (2026-03-27)431432- `ThreadConversation.vue` uses a custom Markdown block parser rather than a standard Markdown library.433- Ordered-list items separated by non-indented paragraphs are parsed into multiple `orderedList` blocks.434- To preserve author-visible numbering in that case, each `orderedList` block needs the original marker value persisted and rendered via the HTML `<ol start=\"...\">` attribute.435## Findings: Dictation / Microphone Feature (2026-02-26)436437- **i18n keys**: `composer.dictation.*` — tooltip is "Hold to dictate", aria is "Dictate".438- **Component**: `M4n` React hook handles recording state, audio capture, and transcription.439- **Audio pipeline**: `navigator.mediaDevices.getUserMedia({ audio: { channelCount: 1 } })` → `MediaRecorder` → chunks → `Blob` → multipart POST.440- **Transcription endpoint**: The renderer sends audio to `/transcribe` via the IPC fetch proxy. The main process (`tle` class) prepends the `prodApiBaseUrl` (`https://chatgpt.com/backend-api`) and attaches ChatGPT auth bearer tokens. Full URL: `https://chatgpt.com/backend-api/transcribe`.441- **Request format**: Multipart form-data with boundary `----codex-transcribe-<uuid>`, fields: `file` (audio blob) and optional `language`. Body is base64-encoded and sent with `X-Codex-Base64: 1` header.442- **Response**: `{ text: "transcribed text" }`.443- **Interaction model**: Press-and-hold to record → release to stop and transcribe → text inserted into composer. Has "insert" and "send" modes.444- **Icon**: `audiowave-dark.svg` / `audiowave-light.svg` (custom SVG, not from icon library).445- **Web app implementation**: Our bridge proxies `/codex-api/transcribe` to the ChatGPT backend using auth tokens from the app-server `getAuthStatus` RPC. Frontend uses `useDictation` composable with `MediaRecorder` API.446447## Findings: Chat Markdown Image Embeds (2026-03-04)448449- Codex.app renderer bundle includes markdown-to-HTML image handling (`image({href,title,text})` emits `<img src="...">`), consistent with inline markdown image rendering in assistant/user text.450- In web parity mode, absolute local paths in markdown image URLs need explicit server mediation; browser runtime does not resolve `/Users/...` as local files.451- A dedicated local image endpoint (`/codex-local-image?path=...`) is required for parity-like rendering of absolute filesystem image paths in browser-delivered UI.452- Express `sendFile` must allow dot-directory segments (`dotfiles: 'allow'`) or paths under `~/.codex/...` return 404 despite existing files.453454## Findings: Composer Enter Behavior (2026-03-05)455456- Codex.app composer input is rich-text/multiline (`ProseMirror`-based), not single-line.457- Enter handling is configurable (`enterBehavior`):458 - `enter` submits by default.459 - `newline` inserts a newline on Enter.460 - `cmdIfMultiline` inserts newline when multiline, otherwise submits.461- Newline shortcuts are explicitly bound:462 - `Shift-Enter` inserts newline.463 - `Alt-Enter` inserts newline.464 - `Mod-Enter` submits.465- This confirms multiline composition parity requires newline-capable input plus explicit Enter-vs-newline key handling.466467## Findings: Composer `@` Mentions (2026-03-05)468469- Codex.app uses a dedicated mention trigger plugin for `@` with pattern `/(^|\s)(@[^\s@]*)$/`, so mentions activate at word boundaries and stop on whitespace or a second `@`.470- Mention entries are stored as an inline `mention-ui` node with attrs `{ label, path, fsPath }`, rendered with data attributes `at-mention-label`, `at-mention-path`, and `at-mention-fs-path`.471- Mention picker keyboard behavior includes:472 - `Escape` closes mention UI.473 - `Enter` and `Tab` commit the highlighted mention.474- Composer placeholder copy in local mode explicitly documents this affordance: `Ask Codex anything, @ to add files, / for commands`.475476## Findings: Thread Rename Flow (2026-03-12)477478- Codex.app locale keys confirm sidebar rename flow is dialog-based, not inline:479 - `sidebarElectron.renameThread`480 - `sidebarElectron.renameThreadDialogTitle`481 - `sidebarElectron.renameThreadDialogSubtitle`482 - `sidebarElectron.renameThreadDialogPlaceholder`483 - `sidebarElectron.renameThreadDialogSave`484 - `sidebarElectron.renameThreadDialogCancel`485 - `sidebarElectron.renameThreadDialogAriaLabel`486- App-server RPC for rename uses method `thread/name/set` with params `{ threadId, name }` (not `threadName`).487- `thread/name/updated` realtime notification carries `{ threadId, threadName }`, so parity implementations should handle both request/response naming differences (`name` on write, `threadName` on notification).488489## Findings: Local Parity Fallback (2026-03-27)490491- In this workspace, `/Applications/Codex.app/Contents/Resources/app.asar` was not present, so Codex.app-first inspection could not run.492- For user-visible changes under this constraint, use the skill's fallback path explicitly: preserve existing repository interaction patterns, keep the UX conservative, and call out the parity blocker in the completion report.493494## Findings: Settings Account Labels (2026-03-24)495496- No equivalent multi-account switcher UI was found in the installed Codex.app bundle for Settings/account list behavior, so parity work should follow the existing local Settings visual language instead of inventing a separate header menu.497- When showing multiple saved accounts that share the same email, account/workspace identity needs its own dedicated label line; folding the workspace identifier into secondary metadata and truncating it makes entries indistinguishable in narrow sidebars.498499## Findings: Mobile Install Icons (2026-03-23)500501- In this web workspace, mobile home-screen installation depends on both `link[rel="apple-touch-icon"]` in `index.html` and the PNG entries in `public/manifest.webmanifest`; updating only the manifest is not enough for iPhone-style add-to-home-screen flows.502- Small PNGs generated from SVG via headless Chrome can silently degrade to all-white images when the SVG is loaded indirectly during screenshot capture. Rendering a reliable large PNG first and deriving smaller sizes from that output avoids blank icon assets.503- For non-transparent exported PNG icons, any transparent margin around the SVG is rasterized against the page background. Set an explicit dark page background during capture or use full-bleed icon artwork to avoid white edges that make installed icons look washed out.504- A more reliable fix than screenshot capture is to rasterize SVGs through a browser canvas (`Image` + `drawImage` + `canvas.toDataURL()`), which preserves the real SVG bounds and avoids dark corner contamination from the page background.505506## Findings: PWA Packaging Fallback (2026-03-23)507508- This repository can be made installable as a browser app without changing runtime behavior by adding standard PWA assets:509 - HTML manifest link + theme color metadata510 - production-only service worker registration in `src/main.ts`511 - static `manifest.webmanifest`512 - static icons under `public/icons/`513- Codex.app desktop parity could not be inspected in this environment because `/Applications/Codex.app` was unavailable, so PWA packaging should follow the fallback path and avoid speculative UX changes.514- A conservative service worker strategy for this repo is:515 - bypass `/codex-api/*` and local file proxy endpoints516 - use navigation fallback to cached `/`517 - use runtime caching for same-origin static assets and manifest518519## Findings: Plan Mode Turn Start (2026-03-22)520521- App-server rejects `turn/start.collaborationMode` unless the client advertises `initialize.capabilities.experimentalApi = true`.522- `turn/start.collaborationMode.settings.model` must be a non-empty concrete model id. Sending `""` can leave a plan-mode thread stuck or fail without rendering plan output.523- In this environment, `collaborationMode/list` returns `Plan` with `mode: "plan"` and `reasoning_effort: "medium"`, but `model` is `null`, so the client must source the actual model from current config or available models before starting the turn.524525## Findings: Account Rate Limits Protocol (2026-03-21)526527- App-server exposes quota state via `account/rateLimits/read` and pushes live updates with `account/rateLimits/updated`.528- Read responses can include `rateLimitsByLimitId.codex`; if absent, fall back to the legacy top-level `rateLimits` bucket.529- Runtime payloads use camelCase fields:530 - snapshot: `limitId`, `limitName`, `planType`, `primary`, `secondary`, `credits`531 - window: `usedPercent`, `windowDurationMins`, `resetsAt`532 - credits: `hasCredits`, `unlimited`, `balance`533- For compact composer display, a conservative summary can be derived from `primary`/`secondary` windows without forcing a full account panel.534- Weekly refresh copy can be derived entirely client-side by selecting the quota window whose `windowDurationMins` is `10080` (or the nearest longer weekly-like window) and formatting its `resetsAt` timestamp into a calendar date for the tooltip.535- On touch/mobile surfaces, quota details hidden only in a `title` attribute are effectively invisible. Weekly refresh information needs to be rendered as visible text in the composer quota badge, not only in hover-only tooltip content.536- For compact inline display, the weekly refresh segment should be date-only (for example `Mar 28` / `3月28日`) and appended on the same line as the quota summary instead of using a separate explanatory label.537538## Findings: Empty Project Removal Persistence (2026-03-21)539540- In this web UI, empty project groups can be recreated purely from persisted workspace-root state, even when no threads exist for that project.541542## Findings: Mobile Foreground Resume (2026-03-23)543544- In this web workspace, a conservative mobile-only freshness policy can be implemented at the app shell level (`App.vue`) by combining:545 - `document.visibilitychange` to record background entry546 - `window.pageshow` to catch BFCache restores547 - `window.focus` as a final foreground fallback548- Restricting the behavior to the existing `<768px` mobile breakpoint avoids forcing reloads on tablet/desktop layouts during tab focus changes.549- A small hidden-duration threshold helps avoid accidental reloads from transient overlays while still reloading after real app switches.550- The persistence source of truth is still the global state keys:551 - `electron-saved-workspace-roots`552 - `electron-workspace-root-labels`553 - `active-workspace-roots`554- Removing a project must delete matching workspace-root entries from all three persisted collections; updating in-memory order alone is insufficient because hydration will rebuild an empty placeholder group on refresh.555- The placeholder group is produced by `orderGroupsByProjectOrder(...)`, which materializes `{ projectName, threads: [] }` when a persisted project name has no matching incoming thread group.556557## Findings: Markdown Block Rendering Fallback (2026-03-21)558559- Codex.app could not be inspected in this Linux environment because `/Applications/Codex.app` is unavailable.560- Conservative fallback for message markdown rendering is to keep the existing inline parser and add a lightweight block parser in `ThreadConversation.vue`.561- A low-risk split that matches existing web UI structure is:562 - block-level parsing for paragraphs, unordered lists, ordered lists, and inline markdown images563 - inline parsing reused for bold, inline code, URLs, and file links564- This avoids introducing a full markdown dependency while fixing the most visible raw-markup regressions (`- item`, `1. item`, `**bold**`).565- Expanded fallback block support that still fits this local parser architecture:566 - headings (`#` ... `######`)567 - blockquotes (`> quote`)568 - task lists (`- [ ]`, `- [x]`)569 - thematic breaks (`---`, `***`, `___`)570 - fenced code blocks (``` / ~~~)571- To avoid breaking local-image rendering and file-link handling, code-fence splitting should happen before inline image token splitting, otherwise `` inside fenced code can be misparsed as a real image block.572573## Findings: Mobile Composer Auto-Zoom Fallback (2026-03-21)574575- Codex.app could not be inspected in this environment, so mobile zoom behavior was handled with a browser-level fallback.576577## Findings: Plan Mode Fallback Wiring (2026-03-22)578579- Codex.app could not be inspected in this Linux environment because `/Applications/Codex.app` is unavailable, so plan-mode behavior was aligned to the shipped app-server protocol instead of renderer-bundle parity.580- App-server protocol already exposes the full plan-mode surface needed by the web UI:581 - `TurnStartParams.collaborationMode`582 - `ModeKind = "plan" | "default"`583 - `turn/plan/updated`584 - `item/plan/delta`585 - `ThreadItem.type = "plan"`586 - `item/tool/requestUserInput`587- Conservative web fallback for plan mode:588 - persist the selected collaboration mode locally under `codex-web-local.collaboration-mode.v1`589 - send `collaborationMode: { mode: "plan", settings: { model, reasoning_effort, developer_instructions: null } }` only when plan mode is selected590 - omit `collaborationMode` entirely for default mode to disable plan mode cleanly591- `collaborationMode/list` can be treated as advisory rather than authoritative for web fallback:592 - when available, use server labels for `default` / `plan`593 - still keep static `Default` and `Plan` options available so the feature remains usable against servers that lag the preset-list endpoint594- `request_user_input` questions need broader handling than the original approval-like UI:595 - support selectable options with descriptions596 - support free-text questions when `options` is `null` or empty597 - support secret answers via password inputs when `isSecret` is true598- On mobile browsers, especially iOS Safari, focusing text inputs below `16px` commonly triggers viewport auto-zoom.599- In this repo, the main composer textarea used `text-sm` (`14px` computed on mobile), which is sufficient to trigger that browser behavior.600- Conservative fix: keep viewport meta unchanged and raise focusable text input font-size to `16px` on mobile widths, instead of disabling pinch zoom globally.601602## Findings: Dark Markdown Theme Coverage Fallback (2026-03-21)603604- Codex.app could not be inspected in this environment, so dark-mode markdown behavior was aligned using existing web theme conventions.605- When adding new markdown block renderers in `ThreadConversation.vue`, matching `:root.dark` overrides must also be added in `style.css`; otherwise the new nodes inherit light-theme slate colors and become low-contrast in dark mode.606- The markdown-specific classes that require explicit dark coverage in the current UI are:607 - headings (`.message-heading`, `.message-heading-h6`)608 - emphasis (`.message-bold-text`, `.message-italic-text`, `.message-strikethrough-text`)609 - blockquote/list/task styles (`.message-blockquote`, `.message-list`, `.message-task-checkbox`)610 - fenced code and divider styles (`.message-code-block`, `.message-code-language`, `.message-divider`)611- A safe fallback pattern is to keep foreground text near existing dark message colors (`zinc-100`/`zinc-200`) and move structural surfaces to darker zinc backgrounds, so markdown blocks remain legible without deviating from the current dark theme.612613## Findings: Web Title Branding Fallback (2026-03-21)614615- Codex.app could not be inspected in this environment, so title branding was aligned using the existing web entry points.616- The browser tab title for the617618…(truncated)