Agent skill
codex-app-parity
Use when implementing or changing user-visible behavior/UI in this repository and parity with the installed Codex desktop app must be validated before coding.
Install this agent skill to your Project
npx add-skill https://github.com/friuns2/codexUI/tree/main/.agents/skills/codex-app-parity
SKILL.md
Codex App Parity Skill
Use this skill for any feature work or user-visible behavior/UI change in this repository. Do not use it for purely internal refactors that do not affect behavior.
Objective
Ensure behavior is implemented with Codex.app as the source of truth, then verified with headless Playwright and screenshots.
Project Instructions
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 patterncomponent.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 theHwn=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.
Architecture Notes
- Renderer → Main Process: The renderer uses a
UuHTTP client class that sendsfetch-requestIPC messages to the main process. The main process classtlehandles these, adds auth tokens, and useselectron.net.fetchto make actual HTTP calls. - Auth: Auth tokens come from the app-server's
getAuthStatusRPC method (ChatGPT backend auth). - App-server: A
codex app-serverchild 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.
- 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.
Response Requirements (When delivering feature changes)
For feature tasks, include:
Codex.app analysis: what was inspected (files/areas/patterns).Parity result: matched items and any explicit deviations.Fallback noteonly 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 result and display that screenshot in chat when reporting completion.
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-labelsactive-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/requestApprovalanditem/fileChange/requestApproval, but the generated event typings also include newer approval event names such asexec_approval_requestandapply_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 olderthreadId/itemIdrequest 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.0also includes JSON-RPC server requests formcpServer/elicitation/requestanditem/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 viacodex 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
falseor the first enum option changes the meaning of the user’s response. - For MCP
urlelicitation mode, treat the server-provided URL as untrusted input and only render clickable links for safe schemes such ashttp:andhttps:.
Findings: Pinned Thread Persistence (2026-04-07)
- This workspace now persists pinned sidebar threads in Codex global state (
~/.codex/.codex-global-state.json) under keythread-pinned-ids. - Bridge API endpoints added for web/client parity wiring:
GET /codex-api/thread-pins->{ data: { threadIds: string[] } }PUT /codex-api/thread-pinswith body{ threadIds: string[] }
- Frontend behavior:
- Sidebar bootstraps pins from
thread-pinned-idsvia the bridge endpoint. - No
localStoragepersistence is used for pinned-thread state.
- Sidebar bootstraps pins from
Findings: Context Usage Meter (2026-04-01)
- Official
openai/codexapp-server protocol exposes per-thread context telemetry viathread/tokenUsage/updatedwith:tokenUsage.totaltokenUsage.lasttokenUsage.modelContextWindow
- In the official TUI, context-window percentage is derived from
last_token_usage, not cumulativetotal_token_usage. - Official normalization subtracts a fixed
BASELINE_TOKENS = 12000before computing remaining context percentage, so early turns do not look artificially "used". - Official status/context copy found in the TUI favors:
X% leftY usedZ window
- When docs are blocked, the quickest parity trace for this feature is:
codex-rs/app-server-protocol/schema/typescript/v2/ThreadTokenUsage*.tscodex-rs/tui/src/chatwidget.rscodex-rs/protocol/src/protocol.rs
Findings: File Change Turn Summaries (2026-03-30)
- Official app-server docs in
openai/codexconfirm that:turn/diff/updatedcarries{ threadId, turnId, diff }as the latest aggregated unified diff for the whole turn.fileChangethread items carry{ id, changes, status }.- Each
changesentry is{ path, kind, diff }.
- For persisted/history-backed UI summaries, prefer
fileChangethread items over reconstructing state from deltas:kindmaps to add/delete/update.updatemay includemove_pathfor rename/move handling.item/completedis the authoritative final state for whether edits actually applied.
- For user-facing file summaries, treat
turn/diff/updatedas a supplemental aggregated diff source, not the only source:- pure rename/move flows may not emit a meaningful turn diff payload for summary text.
fileChangeitems 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/forksupport in v2, so UI work should call the RPC directly instead of simulating a new thread locally. ThreadForkParams.pathis documented as an unstable rollout-path override, whilethreadIdremains 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/forkfor the source thread - then call
thread/rollbackon the new thread for trailing turns after the chosen answer
- call
- 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/titleoverpreview; 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.vueuses a custom Markdown block parser rather than a standard Markdown library.- Ordered-list items separated by non-indented paragraphs are parsed into multiple
orderedListblocks. - To preserve author-visible numbering in that case, each
orderedListblock 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:
M4nReact 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
/transcribevia the IPC fetch proxy. The main process (tleclass) prepends theprodApiBaseUrl(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 optionallanguage. Body is base64-encoded and sent withX-Codex-Base64: 1header. - 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/transcribeto the ChatGPT backend using auth tokens from the app-servergetAuthStatusRPC. Frontend usesuseDictationcomposable withMediaRecorderAPI.
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
sendFilemust 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):entersubmits by default.newlineinserts a newline on Enter.cmdIfMultilineinserts newline when multiline, otherwise submits.
- Newline shortcuts are explicitly bound:
Shift-Enterinserts newline.Alt-Enterinserts newline.Mod-Entersubmits.
- 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-uinode with attrs{ label, path, fsPath }, rendered with data attributesat-mention-label,at-mention-path, andat-mention-fs-path. - Mention picker keyboard behavior includes:
Escapecloses mention UI.EnterandTabcommit 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.renameThreadsidebarElectron.renameThreadDialogTitlesidebarElectron.renameThreadDialogSubtitlesidebarElectron.renameThreadDialogPlaceholdersidebarElectron.renameThreadDialogSavesidebarElectron.renameThreadDialogCancelsidebarElectron.renameThreadDialogAriaLabel
- App-server RPC for rename uses method
thread/name/setwith params{ threadId, name }(notthreadName). thread/name/updatedrealtime notification carries{ threadId, threadName }, so parity implementations should handle both request/response naming differences (nameon write,threadNameon notification).
Findings: Local Parity Fallback (2026-03-27)
- In this workspace,
/Applications/Codex.app/Contents/Resources/app.asarwas 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"]inindex.htmland the PNG entries inpublic/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.appwas 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
- bypass
Findings: Plan Mode Turn Start (2026-03-22)
- App-server rejects
turn/start.collaborationModeunless the client advertisesinitialize.capabilities.experimentalApi = true. turn/start.collaborationMode.settings.modelmust 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/listreturnsPlanwithmode: "plan"andreasoning_effort: "medium", butmodelisnull, 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/readand pushes live updates withaccount/rateLimits/updated. - Read responses can include
rateLimitsByLimitId.codex; if absent, fall back to the legacy top-levelrateLimitsbucket. - Runtime payloads use camelCase fields:
- snapshot:
limitId,limitName,planType,primary,secondary,credits - window:
usedPercent,windowDurationMins,resetsAt - credits:
hasCredits,unlimited,balance
- snapshot:
- For compact composer display, a conservative summary can be derived from
primary/secondarywindows without forcing a full account panel. - Weekly refresh copy can be derived entirely client-side by selecting the quota window whose
windowDurationMinsis10080(or the nearest longer weekly-like window) and formatting itsresetsAttimestamp into a calendar date for the tooltip. - On touch/mobile surfaces, quota details hidden only in a
titleattribute 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.visibilitychangeto record background entrywindow.pageshowto catch BFCache restoreswindow.focusas a final foreground fallback
- Restricting the behavior to the existing
<768pxmobile 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-rootselectron-workspace-root-labelsactive-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.appis 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 (``` / ~~~)
- headings (
- 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.appis 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.collaborationModeModeKind = "plan" | "default"turn/plan/updateditem/plan/deltaThreadItem.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
collaborationModeentirely for default mode to disable plan mode cleanly
- persist the selected collaboration mode locally under
collaborationMode/listcan be treated as advisory rather than authoritative for web fallback:- when available, use server labels for
default/plan - still keep static
DefaultandPlanoptions available so the feature remains usable against servers that lag the preset-list endpoint
- when available, use server labels for
request_user_inputquestions need broader handling than the original approval-like UI:- support selectable options with descriptions
- support free-text questions when
optionsisnullor empty - support secret answers via password inputs when
isSecretis true
- On mobile browsers, especially iOS Safari, focusing text inputs below
16pxcommonly triggers viewport auto-zoom. - In this repo, the main composer textarea used
text-sm(14pxcomputed on mobile), which is sufficient to trigger that browser behavior. - Conservative fix: keep viewport meta unchanged and raise focusable text input font-size to
16pxon 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.darkoverrides must also be added instyle.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)
- headings (
- 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 main app comes from
index.html. - The unauthenticated/login page has its own inline HTML template in
authMiddleware.ts, so branding changes need to update both places to stay consistent.
Findings: Sidebar Thread Menu Clickability Fallback (2026-03-21)
- Codex.app could not be inspected in this environment, so thread-menu behavior was aligned using stable web popover patterns.
- In this sidebar implementation, thread menus are rendered inside the
right-hoverslot ofSidebarMenuRow; if the row stops being hovered and no explicit open-state override is applied, that slot can collapse and make the menu hard to interact with. - Outside-click dismissal must include
openThreadMenuIdin the shared dismiss-listener condition. Otherwise thread menus fall back to ad-hoc closing logic and can behave inconsistently. - For rows near the bottom of the scrollable sidebar, the menu should choose its open direction based on available space in the nearest overflow-clipping ancestor rather than always opening downward.
- A low-risk fallback is:
- keep the row in
forceRightHovermode while its menu is open - raise the open row above siblings with a higher z-index
- auto-flip the menu upward when the scroll container has less space below than above
- keep the row in
Findings: Thread Delete Semantics (2026-03-12)
- In this app-server API surface there is no
thread/deletemethod in v2 docs/schemas; thread removal from active list is handled throughthread/archive. - For delete-like UI parity in sidebar menus, implement a destructive confirmation dialog and route confirmation to
thread/archive.
Findings: Build Badge (2026-03-16)
- Searched extracted Codex.app webview assets for
build-badge,WT, andworktreeUI markers; no explicit build badge or worktree/version label found in renderer bundle.
Findings: Parity Extraction Sanity Check (2026-03-27)
- In this environment,
/Applications/Codex.appmay be absent while/tmp/codex-app-extractedstill exists as an empty directory from a prior session. - Before relying on extracted bundle searches, verify both the app bundle path and that the extraction target actually contains files.
- When both checks fail for a UI change, treat parity inspection as blocked, use existing repository behavior as the fallback baseline, and report that gap explicitly in the final response.
Findings: Message Action Removal Fallback (2026-03-27)
- With Codex.app unavailable for inspection, message-action removals should be implemented as full UI deletion, not partial hiding.
- In this codebase, message actions span both template nodes in
ThreadConversation.vueand shared dark-theme overrides instyle.css; removing only the button markup leaves dead hover/dark styles behind. - A safe fallback cleanup is to remove the template block, the helper functions/imports that feed it, and the corresponding
.message-action*selectors together in the same change.
Findings: Detached HEAD Worktree Creation (2026-04-08)
- Codex.app worker bundle creates new worktrees with detached HEAD semantics:
git worktree add --detach <worktreePath> <startRef>
- Evidence location in extracted app bundle:
/tmp/codex-app-extracted/.vite/build/worker.js(Xq(...)flow)
- Parity implication for this repo:
- New worktree creation should not create/switch to a new local branch by default.
- API responses should treat branch as nullable/absent for detached worktrees.
Didn't find tool you were looking for?