This file tracks manual regression and feature verification steps.
- <cleanup action, if any>
- App server is running from this repository.
- A valid Telegram bot token is available.
- At least one Telegram user ID is available for allowlisting.
- Access to
~/.codex/on the host machine.
- In the app UI, open Telegram connection and submit a bot token plus one or more allowed Telegram user IDs.
- Verify file
~/.codex/telegram-bridge.jsonexists. - Open
~/.codex/telegram-bridge.jsonand confirm it containsbotTokenandallowedUserIdsfields. - Restart the app server and call Telegram status endpoint from UI to confirm it still reports configured.
- Telegram token is persisted in
~/.codex/telegram-bridge.json. - Telegram allowlisted user IDs are persisted in
~/.codex/telegram-bridge.json. - Telegram bridge remains configured after restart.
- Remove
~/.codex/telegram-bridge.jsonto clear saved Telegram token.
- App server is running from this repository.
- Telegram bot already configured in the app.
- Access to
~/.codex/telegram-bridge.json.
- Send
/startto the Telegram bot from your DM. - Wait for the app to process the update, then open
~/.codex/telegram-bridge.json. - Confirm
chatIdscontains your DM chat id as the first element. - In the app, reconnect Telegram bot with the same token.
- Re-open
~/.codex/telegram-bridge.jsonand confirmchatIdsremains present.
chatIdsis written after Telegram DM activity.chatIdspersists across bot reconfiguration.botToken,chatIds, andallowedUserIdsare all present in~/.codex/telegram-bridge.json.
- Remove
chatIdsor delete~/.codex/telegram-bridge.jsonto clear persisted chat targets.
- App server is running from this repository.
- Telegram bot is configured with a known
allowedUserIdsentry. - One Telegram account is allowlisted and one separate Telegram account is not.
- From the allowlisted Telegram account, send
/startto the bot. - Confirm the bot responds normally.
- From the non-allowlisted Telegram account, send
/startto the same bot. - From the non-allowlisted account, send a normal text prompt.
- The allowlisted account can use the Telegram bridge normally.
- The non-allowlisted account receives an unauthorized response.
- No thread is created or updated for the non-allowlisted account.
- Remove test chat mappings from
~/.codex/telegram-bridge.jsonif needed.
- App is running from this repository.
- At least one thread exists and can be selected.
- At least one installed skill is available.
- Open an existing thread so the message composer is enabled.
- Click the
Skillsdropdown in the composer footer. - Click any skill option in the dropdown list.
- Re-open the
Skillsdropdown and click the same skill again to unselect it.
- The skills dropdown closes immediately after each selection click.
- Selected skill appears as a chip above the composer input when checked.
- Skill chip is removed when the skill is unchecked on the next selection.
- Remove the selected skill chip(s) before leaving the thread, if needed.
- App is running from this repository.
- Open the
Skills Hubview.
- Type a unique query value in the Skills Hub search input (for example:
docker), but do not press Enter or click Search yet. - Confirm the browse results do not refresh immediately while typing.
- Click the
Searchbutton. - Change the query text to another value and press Enter in the input.
- Clear the query, then click
Searchto reload the default browse list.
- Typing alone does not trigger remote Skills Hub search requests.
- Results refresh only after explicit submit via the
Searchbutton or Enter key. - Empty-state text (if shown) references the last submitted query.
- Submitting an empty query returns the default skills listing.
- Clear the search input and run a blank search to return to default listing.
- App is running from this repository.
- Home/new-thread screen is open.
- Appearance is set to
Darkin Settings. GitHub trending projectssetting is enabled.
- On the home/new-thread screen, inspect the
Choose folderdropdown trigger. - Open the
Choose folderdropdown and confirm menu/option contrast remains readable in dark mode. - Inspect the
Trending GitHub projectssection title, scope dropdown, and project cards. - Hover a trending project card and the scope dropdown trigger.
- Toggle appearance back to
Light, then return toDark.
- Local project dropdown trigger/value uses dark theme colors with readable contrast.
- Trending section title, empty/loading text, scope dropdown, and cards use dark backgrounds/borders/text.
- Hover states in dark mode stay visible and do not switch to light backgrounds.
- Theme switch back/forth preserves correct styling for both controls.
- Reset appearance to the previous user preference.
- App is running from this repository.
- Appearance is set to
Darkin Settings. - Skills Hub route is accessible.
- Open the home/new-thread screen and inspect the
Local project / New worktreeruntime selector trigger. - Open the runtime selector and verify menu title, options, selected state, and checkmark visibility in dark mode.
- Trigger a worktree action that shows worktree status and verify running/error status blocks remain readable in dark mode.
- Open
Skills Huband verify header/subtitle, search bar, search/sort buttons, sync panel, badges, and status text. - Verify at least one skill card surface (title, owner, description, date, browse icon) in dark mode.
- Open a skill detail modal and verify panel, title/owner, close button, README/body text, and footer actions in dark mode.
- Runtime dropdown trigger and menu use dark backgrounds, borders, and readable text/icons.
- Worktree status blocks use dark-friendly contrast for both running and error states.
- Skills Hub controls and sync panel are fully dark-themed with consistent hover/active states.
- Skill cards and the skill detail modal render with dark theme colors and accessible contrast.
- Reset appearance to the previous user preference.
- App is running from this repository.
- An active thread is open.
- Local file exists at
/root/New Project (1)/qwe.txt.
- Send a message containing:
Done. Created [/root/New Project (1)/qwe.txt](/root/New Project (1)/qwe.txt) with content:. - In the rendered assistant message, click the
/root/New Project (1)/qwe.txtlink. - Right-click the same link and choose
Copy linkfrom the context menu. - Paste the copied link into a text field and inspect it.
- The markdown link renders as one clickable file link (not split into partial tokens).
- Clicking opens the local browse route for the full file path.
- Copied link includes the full encoded path and still resolves to the same file.
- Delete
/root/New Project (1)/qwe.txtif it was created only for this test.
- App is running from this repository.
- Home/new-thread screen is open.
- On the home/new-thread screen, locate the runtime control below
Choose folder. - Verify both options (
Local projectandNew worktree) are visible at once without opening a menu. - Click
New worktreeand confirm it becomes the selected option style. - Click
Local projectand confirm selection returns. - Set Appearance to
Darkin Settings and verify selected/unselected contrast remains readable.
- Runtime mode is presented as a two-option toggle (segmented control), not a dropdown menu.
- Clicking each option immediately switches the selected state.
- Selected option has a distinct active background/border in both light and dark themes.
- Leave runtime mode and appearance at the previous user preference.
- App is running from this repository.
- Home/new-thread screen is open.
- Appearance is set to
Darkin Settings.
- Locate the runtime mode toggle (
Local projectandNew worktree) underChoose folder. - Hover each option and verify hover state is visible against dark backgrounds.
- Select
New worktree, then selectLocal projectand compare active/inactive contrast. - Tab to the toggle options with keyboard navigation and verify the focus ring is visible.
- Confirm icon color remains readable for selected and unselected options.
- Toggle container, options, and text/icons use dark-friendly colors.
- Hover and selected states are clearly distinguishable in dark mode.
- Keyboard focus ring is visible and does not blend into the background.
- Return appearance and runtime selection to the previous user preference.
- App is running from this repository against a Codex app-server that supports thread-scoped model persistence.
- At least two selectable models are available in the composer model picker.
- At least one existing thread is available, or you can create one during the test.
- On the new-thread screen, choose model
Ain the composer. - Send a message to create a new thread.
- In that thread, switch the composer model to model
B. - Send another message in the same thread so the thread persists model
B. - Create or open a different thread and set its model to model
A. - Switch back and forth between the two threads.
- Refresh the browser while one of the threads is selected.
- Re-open both threads again after the refresh.
- While thread
Ais selected, use the sidebar thread menu to fork threadB. - Open the forked thread and confirm the composer model matches thread
B, not the currently selected thread. - Restart the app-server or otherwise force a model-list refresh that does not include one thread’s persisted model, then switch back to that thread.
- Delete one of the test threads you changed, refresh the thread list, and continue switching between the remaining thread and the new-thread screen.
- Each thread restores its own last selected model when you switch threads.
- The new-thread screen keeps its own draft model selection instead of inheriting the last opened thread.
- After browser refresh, reopening a thread restores the model persisted for that thread.
- Forked or newly created threads keep the resolved model returned by Codex, including fallback to the supported default model when needed.
- Forking a nonselected thread from the sidebar uses that source thread’s persisted model.
- If the selected thread’s persisted model is not returned in the latest model list, the composer still shows that model as the active selection instead of falling back to the placeholder label.
- Removing a thread prunes its saved per-thread model state, and model selection continues to update normally for the remaining threads without runtime errors.
- Reset each tested thread back to its original model selection if you changed an existing conversation for the test.
- App is running from this repository with a Codex CLI/app-server version that can request approvals.
bubblewrapis installed so sandboxed command approvals can be triggered.- Approval policy is set to request approval on sandbox escalation.
- Start a thread and ask Codex to run a command that requires approval outside the current sandbox.
- Wait for the pending request panel to appear.
- Confirm the request is shown as an approval prompt, not the generic fallback with
Return Empty ResultandReject Request. - Verify the panel offers approval choices (
Yes,Yes for Session, decline text field,Skip). - If the approval payload includes a command preview or writable root, verify that preview text is shown in the panel.
- Sandbox-related approval requests are classified as approvals even when Codex sends newer method or payload variants.
- The approval UI offers normal approval actions instead of the unknown-request fallback buttons.
- The request stays attached to the correct thread rather than only appearing as a global pending request.
- Decline or skip the pending approval request after verification.
- App is running from this repository with a recent Codex CLI/app-server build.
- At least one configured MCP server can trigger
mcpServer/elicitation/requestoritem/permissions/requestApproval.
- Start a thread and trigger an MCP flow that asks for user input or permission approval.
- Confirm the thread row status chip in the sidebar appears in English (
Awaiting approvalorAwaiting response). - Open the pending request panel for
mcpServer/elicitation/request. - Confirm only the black pending-request panel is shown for the request; no duplicate yellow in-conversation request card should appear.
- If the elicitation is
formmode, verify the requested fields are rendered as inputs/selects/checkboxes based on the schema. - For a required form field that has no schema default, click
Continuewithout answering it and verify the request stays open with a validation error instead of submitting a fabricated answer. - For an optional boolean or enum field that has no schema default, verify the control starts unselected rather than prefilled with
Falseor the first enum option. - If the elicitation is
urlmode, verify an authorization link is shown only when the URL useshttporhttps. - Submit
Continue, then repeat and verifyDeclineandCancelare also available. - Trigger an
item/permissions/requestApprovalrequest and verifyAcceptandAccept for Sessionare shown instead of the generic fallback buttons.
- MCP elicitation requests no longer fall back to
Return Empty Result/Reject Request. - Pending requests are shown only once, in the dedicated black pending-request panel.
- Form-mode elicitation requests submit structured
{ action, content }responses. - Required MCP form fields without defaults must be answered explicitly before the request can be accepted.
- Optional MCP boolean/enum fields without defaults remain unset until the user chooses a value.
- URL-mode elicitation requests show an authorization link and submit a valid
{ action }response. - Non-HTTP(S) authorization URLs are not rendered as clickable links.
- Permissions approval requests submit proper permission grants with turn/session scope.
- Sidebar pending-request chips are displayed in English.
- Decline or cancel the MCP request after verification, and close any opened authorization URL if it was only used for testing.
- App is running from this repository via CLI.
- A Tailscale client can reach the host over Tailscale IPv4 (
100.64.0.0/10) or IPv6 (fd7a:115c:a1e0::/48). cloudflaredis installed only if testing--tunnel.
- Start CLI without tunnel flag:
npx codexapp --port 5900. - From a Tailscale client, open
http://100.x.x.x:5900using a host address in100.64.0.0/10(replace with host tailnet IP). - Confirm the app opens directly without the password login page.
- (Optional IPv6 check) Open the same service using the host Tailscale IPv6 address in
fd7a:115c:a1e0::/48and confirm it also bypasses password. - Stop the server and start again with tunnel enabled:
npx codexapp --port 5900 --tunnel. - Confirm startup output now includes a
Tunnel:URL only when--tunnelis provided. - Stop and restart once more without
--tunnel, and verify no tunnel URL is printed.
- Requests from Tailscale IPv4
100.64.0.0/10are treated as trusted and do not require password sign-in. - Requests from Tailscale IPv6
fd7a:115c:a1e0::/48are treated as trusted and do not require password sign-in. - Cloudflare tunnel does not start by default.
- Cloudflare tunnel starts only when
--tunnelis explicitly passed.
- Stop the CLI process.
- If a cloudflared tunnel was started, ensure the tunnel child process has exited.
- App is running from this repository via CLI.
- One environment with detected Tailscale IP (
100.64.0.0/10orfd7a:115c:a1e0::/48) and one without (or simulated by disabling Tailscale).
- Start server without explicit tunnel flags:
npx codexapp --port 5900. - In a host where Tailscale IP is detected, verify startup output includes
Tunnel:. - In a host where Tailscale IP is not detected, verify startup output does not include
Tunnel:. - Start server with explicit override
--no-tunneland verify noTunnel:output even when Tailscale IP is present. - Start server with explicit override
--tunneland verifyTunnel:output even when Tailscale IP is not present.
- Without explicit flags, tunnel enablement follows Tailscale IP detection.
--no-tunnelalways disables tunnel startup.--tunnelalways enables tunnel startup.
- Stop the CLI process after each verification run.
- Ensure cloudflared child process exits after shutdown.
- App is running with password enabled.
- One direct local browser session (
localhost). - One reverse tunnel path (for example SSH/Cloudflare forwarding) that reaches the same server.
- Optional Tailscale client in
100.64.0.0/10orfd7a:115c:a1e0::/48.
- Open app via
http://localhost:<port>and confirm it opens without login when request is true local loopback. - Open app via reverse-tunnel URL and confirm login page is shown.
- Enter correct password in reverse-tunnel URL and confirm session cookie allows access.
- (Optional) Open app via Tailscale IP and confirm login is bypassed.
- Local loopback access is allowed without login prompt.
- Reverse-tunnel access does not bypass auth and requires password.
- Valid login on reverse-tunnel path creates session and grants access.
- Tailscale CIDR requests remain trusted.
- Clear browser cookies for the app origin(s).
- Stop the CLI process.
- App is running from this repository with password enabled.
- Cloudflare tunnel startup is enabled (
--tunnelor auto-enabled path).
- Start CLI and wait for tunnel output.
- Verify the printed
Tunnel:URL includes/password=suffix. - Scan the terminal QR code from a phone/browser.
- Confirm first page load enters the app without showing password form.
- Open the tunnel base URL without
/password=in a private window and verify login prompt still appears.
- Tunnel URL shown in startup output uses
/password=<encoded-password>. - QR code encodes the same auto-login URL.
- Visiting the auto-login URL sets session cookie and redirects to
/. - Base tunnel URL still requires login when no trusted bypass applies.
- Stop the CLI process.
- Clear cookies for the tunnel origin if needed.
- App is running from this repository.
- At least one existing thread is available.
- Browser local storage is enabled.
- Open the app in a regular browser tab (
http://localhost:<port>/), select any thread, then navigate back to home route (#/). - Refresh the browser tab.
- Confirm the app remains on home route and does not auto-switch to
#/thread/:threadId. - Install/open the app in PWA standalone mode, select any thread, navigate to
#/, and relaunch the PWA.
- In regular browser-tab mode, startup does not restore and redirect to the last active thread.
- In PWA standalone mode, startup also does not restore and redirect to the last active thread.
- Existing
openProjectPathstartup behavior still opens the requested project on home.
- Clear app local storage state if you need to reset startup behavior for retesting.
pnpmis installed globally (npm i -g pnpmor via corepack).- Repository is cloned and
node_modules/does not exist (or may be stale).
- Remove
node_modules/if present:rm -rf node_modules. - Run
pnpm run dev. - Wait for Vite dev server to start and display the local URL.
- Open the displayed URL in a browser.
pnpm installruns automatically before Vite starts (dependencies are installed).- Vite dev server starts successfully and serves the app.
- No
npmcommands are invoked.
- None.
- App server is running from this repository.
- No
CODEXUI_SANDBOX_MODEorCODEXUI_APPROVAL_POLICYenvironment overrides are set for the launch shell.
- Start the app normally from this repository without passing
--sandbox-modeor--approval-policy. - Open the startup logs or terminal output and find the runtime summary.
- Confirm the reported sandbox mode is
workspace-write. - Confirm the reported approval policy is
on-request. - Restart the app with explicit overrides, for example
--sandbox-mode danger-full-access --approval-policy never, and confirm those override the defaults. - With those overrides still active, trigger an account flow that uses the temporary app-server path (for example a quota/account inspection request).
- Confirm the temporary app-server request succeeds under the active override settings and does not behave as if it were still using the original startup defaults.
- Default launch uses
workspace-writesandbox mode. - Default launch uses
on-requestapproval policy. - Explicit CLI flags still override the defaults when provided.
- Temporary app-server spawns in account routes use the current env-derived runtime args, including CLI overrides.
- Remove any temporary CLI overrides before leaving the environment.
- App is running from this repository.
- An active thread is open.
- Send a message containing exactly:
`https://github.com/marmeladema`. - Find the rendered message row and inspect the backticked URL token.
- Click the rendered URL.
- The backticked URL is rendered as a clickable link, not plain inline code text.
- Clicking opens
https://github.com/marmelademain a new tab.
- None.
- App is running from this repository.
- At least one thread can run a long response (for example, request a large code explanation).
- Send a prompt that keeps the assistant generating for several seconds.
- Immediately click the
Stopbutton before the first assistant chunk fully completes. - Confirm generation halts.
- Repeat with a resumed/existing in-progress thread (reload app while a turn is running, then click
Stop).
- No error appears saying
turn/interrupt requires turnId. - Turn is interrupted successfully in both immediate-stop and resumed-thread scenarios.
- Thread state exits in-progress and the stop control returns to idle.
- None.
- App is running from this repository.
- Home/new-thread screen is open.
- At least one writable parent directory exists for creating a test project folder.
- On the home/new-thread screen, open the
Choose folderdropdown. - Click
+ Add new project. - Enter a new folder name (for example
New Project Inline Test) and clickOpen. - Confirm the app selects the newly created/opened project folder.
- Repeat step 2, but enter an absolute path to an existing folder and click
Open.
- Clicking
+ Add new projectopens inline input inside the dropdown instead of navigating to/codex-local-browse.... - Entering a folder name creates/selects that project under the current base directory.
- Entering an absolute path opens that existing folder without creating a nested directory.
- Delete the test folder created in step 3 if it was created only for verification.
- App is running from this repository.
- At least one existing thread is available.
- Browser local storage may contain previous app state.
- Open an existing thread route and confirm messages are visible.
- Open
http://localhost:<port>/(home route) in the same browser profile. - Refresh the home route once.
- Close and re-open the app/tab at the home URL again.
- The app remains on the home/new-thread screen and does not auto-navigate to
/thread/<id>. - Refreshing home still keeps the user on home.
- None.
- App is running from this repository.
- A thread exists with enough messages to scroll.
- Test on a mobile-sized viewport (for example 375x812).
- Open an existing thread and scroll up to the middle of the chat history.
- Wait for an assistant response to stream while staying at the same scroll position.
- Send a follow-up message and observe chat positioning when completion finishes.
- Open the composer on mobile and drag within the composer area.
- Open/close the on-screen keyboard on mobile and verify the page layout remains usable.
- Chat behavior matches pre-PR #16 baseline (no PR #16 scroll-preservation logic active).
- No regressions from reverting PR #16 changes in conversation rendering and composer behavior.
- Mobile layout no longer includes PR #16 visual-viewport sync changes.
- Re-apply PR #16 commits if the reverted behavior is not desired.
- App is running from this repository.
- At least one thread exists with more than 10 turns/messages.
- Open a long thread that previously caused UI lag during initial load.
- While the thread is loading, immediately click another thread in the sidebar.
- Return to the long thread.
- Count visible loaded history blocks and confirm only the newest portion is shown.
- Call
/codex-api/rpcwith methodthread/readfor the same thread and inspectresult.thread.turns.length. - Call
/codex-api/rpcwith methodthread/resumefor the same thread and inspectresult.thread.turns.length.
- Initial thread load renders only the most recent 10 turns.
- UI remains responsive during thread load.
- You can switch to another thread without the UI freezing.
thread/readandthread/resumeRPC responses contain at most 10 turns.
- No cleanup required.
- App is running from this repository.
- Browser DevTools Network tab is open.
- At least two threads exist with different
cwdvalues.
- Reload the app and wait for initial data load.
- In Network tab, inspect
/codex-api/rpcrequests with methodskills/list. - Verify request params contain
cwdswith only the currently selected thread cwd. - Switch to another thread with a different cwd.
- Inspect the next
skills/listrequest and verifycwdsnow contains only the new selected thread cwd.
- App is running from this repository.
- At least two threads exist in the sidebar.
- Pin two threads from the sidebar using the pin button.
- Refresh the app page.
- Confirm the same threads are still shown in the
Pinnedsection and in the same order. - Archive one of the pinned threads from the thread menu.
- Refresh the app page again.
- Pinned threads are restored after reload from Codex app global state (
~/.codex/.codex-global-state.jsonkeythread-pinned-ids). - Pin order is preserved between reloads.
- Archived/removed pinned thread is automatically pruned and no stale pinned row remains.
- Unpin test threads if needed.
skills/listno longer sends every thread cwd in one request.- Each
skills/listcall includes at most one cwd for the active thread context. - Skills list still updates when changing selected thread.
- No cleanup required.
- The
docs/index.htmlfile has been updated with the new design. - A browser is available to view the page locally or via GitHub Pages.
- Open
docs/index.htmlin a browser (local file or via GitHub Pages). - Verify the fixed navigation bar at top with brand logo, section links, and "Get the App" CTA.
- Verify the announcement banner below nav shows the XCodex WASM link.
- Verify hero section displays lobster emoji, "AnyClaw" title with gradient, tagline, and four CTA buttons: "Try Web Demo", "Google Play", "Download APK", "GitHub".
- Click "Try Web Demo" button — confirm it navigates to
https://xcodex.slrv.md/#/. - Verify the stats bar shows key metrics (2 AI Agents, 1 APK, 0 Root Required, 73MB, infinity).
- Scroll to Live Demo section — verify embedded iframe loads
https://xcodex.slrv.md/#/with mock browser chrome. - Scroll to Screenshots section — verify four images render (2 desktop, 2 mobile).
- Scroll to Features section — verify 6 feature cards in a 3-column grid.
- Scroll to Testimonials section — verify two rows of auto-scrolling marquee cards (row 2 scrolls reverse). Hover to pause.
- Scroll through Architecture, Boot Sequence, Quick Start, and Tech Stack sections — verify content renders.
- Verify the footer includes a "Web Demo" link to
https://xcodex.slrv.md/#/. - Test responsive at 768px and 480px — nav links collapse, grids single-column, buttons stack vertically.
- Page has a dark, premium feel with gradient accents, grain overlay, and smooth animations.
- All links to
https://xcodex.slrv.md/#/work (announcement, hero CTA, demo section, quick start text, footer). - Marquee testimonials scroll continuously and pause on hover.
- Embedded iframe demo loads successfully.
- Mobile responsive layout works at all breakpoints.
- Revert
docs/index.htmlto previous commit if needed.
- App is running from this repository.
- A thread exists with enough history to allow scrolling away from bottom.
- Open the thread and scroll upward so latest messages are not visible.
- Send a new message that produces a streaming assistant response.
- During streaming, do not scroll and observe viewport position.
- After streaming completes, verify the viewport remains at the same manual position.
- Streaming updates do not force auto-scroll to the bottom when user has manually scrolled away.
- User can continue reading older history while the response streams.
- If the thread is already at the bottom when streaming starts, the latest streaming overlay remains visible.
- Revert the scroll-preservation change in
src/components/content/ThreadConversation.vueif manual scroll locking needs to be removed.
- App is running from this repository.
- A thread exists with at least 2 completed turns.
- Rollback control is visible in the thread conversation message actions.
- Open any existing thread with multiple turns.
- In DevTools Network tab, keep
/codex-api/rpcrequests visible. - Click rollback on a user or assistant message that is not the newest one.
- Confirm rollback succeeds and the thread is truncated to the selected turn.
- Inspect the UI event flow by repeating rollback from a different turn and confirm the selected message can rollback without relying on a numeric turn index.
- Use dictation resend flow (or "rollback latest user turn" flow) and confirm the latest user turn is rolled back correctly.
- Rollback works when triggered from message actions using
turnIdas the identifier. - No UI path depends on
turnIndexin rollback event payloads. - Latest-user-turn rollback flow still works and targets the latest user
turnId. - No TypeScript/runtime errors are introduced in rollback interaction.
- Revert the updated files if this behavior is not desired:
src/types/codex.tssrc/api/normalizers/v2.tssrc/components/content/ThreadConversation.vuesrc/App.vuesrc/composables/useDesktopState.ts
- App server is running from this repository.
- Use a fresh temporary project directory with no existing
.codex/rollbacks/.githistory.
- In a fresh test project folder, trigger rollback automation init by calling
/codex-api/worktree/auto-commitwith a valid commit message. - Verify rollback repo exists at
.codex/rollbacks/.git. - In that rollback repo, run
git --git-dir .codex/rollbacks/.git --work-tree . show --name-only --pretty=format: HEAD. - Confirm
.codex/.gitignoreappears in the file list for the init commit. - Open
.codex/.gitignoreand verifyrollbacks/exists.
- First rollback-history commit is
Initialize rollback history. - That commit includes
.codex/.gitignore. .codex/.gitignorecontainsrollbacks/.
- Remove the temporary test folder after verification.
- App server is running from this repository.
worktree git automationis enabled in UI settings.- Test thread available where you can send at least 3 user turns.
- Send a user turn that changes files and completes.
- Send a user turn that produces no file edits and completes.
- Send a third user turn and complete it.
- In rollback git history (
.codex/rollbacks/.git), verify each completed turn created a commit, including the no-edit turn. - Inspect one rollback commit body and confirm it contains the user message text plus
Rollback-User-Message-SHA256: <hash>. - Trigger rollback to the second turn message via UI rollback action.
- Verify server logs contain
[rollback-debug]entries for lookup, stash (if dirty), reset, and completion. - Temporarily test missing-commit path by calling
/codex-api/worktree/rollback-to-messagewith a non-existent message text.
- Auto-commit creates a rollback commit for every completed turn (
--allow-emptybehavior). - Commit body includes the user message and stable hash trailer.
- Rollback uses exact hash-based commit lookup only.
- If exact commit is missing, rollback returns error and does not continue.
- Server logs include
[rollback-debug]records for commit creation, lookup, stash, reset, and error paths. - Browser console includes
[rollback-debug]client-side start/success/error logs for auto-commit and rollback API calls. - Rollback init no longer fails when
.codexis ignored globally; init force-adds.codex/.gitignore.
- Revert the changed files if you want previous non-deterministic behavior back.
- App server running from this repository.
- Worktree git automation enabled.
- A thread with at least one completed turn that touched files.
- Open a thread and locate a
Worked for ...separator message. - Expand the worked separator.
- Verify a changed-files panel appears above command details.
- Confirm file list entries show file path and
+/-counts. - Click one changed file row to expand it.
- Verify diff content loads only after expansion (lazy load behavior).
- Collapse and re-expand the same file row; verify diff reuses loaded content.
- Switch to another thread and back; verify panel reloads for the active thread context.
- Each worked message can show changed files for its turn.
- Diff for a file is fetched only on expand, not for all files upfront.
- Errors (missing commit/diff load failure) are shown inline in the panel.
- Existing command output expand/collapse behavior remains unchanged.
- Changed-files panel still resolves after page refresh or app-server restart.
- Changed-files panel appears at the end of the worked message block (after command rows).
- No cleanup required.
- App server running from this repository.
- A thread with at least one
Worked for ...separator.
- Open a thread and locate a
Worked for ...message. - Click the separator line/text area.
- Verify no expand/collapse behavior is triggered on the separator itself.
- Verify changed-files panel still appears below the separator when data exists.
Worked for ...acts as a visual separator only (non-interactive).- Changed-files and command sections are not gated by a worked-separator expand toggle.
- No cleanup required.
- App server running from this repository.
- Playwright CLI available.
- Create/prepare a test workspace (example:
/tmp/rollback-pw). - Call
/codex-api/worktree/auto-commitwith:cwd=/tmp/rollback-pwmessage='pw-msg-turn-1'turnId='turn-real-1'
- Call
/codex-api/worktree/message-changeswith:- same
cwd - same
message - mismatched
turnId='turn-wrong'
- same
- Verify response is still
200and returns the matching commit data (message-hash fallback). - Capture Playwright artifact screenshot.
message-changesfirst attempts turnId lookup.- If turnId lookup misses, it falls back to exact message-hash lookup.
- API returns commit data instead of
No matching commit found for this user messagewhen message matches.
- Remove temporary test workspace if created.
- App server running from this repository.
- Existing thread in
TestChatproject with completed assistant messages. - Worktree rollback auto-commit enabled.
- Open a
TestChatthread and confirm assistant message cards render. - Verify changed-files panel is shown at the end of assistant messages that have rollback commit data.
- Hard refresh the page.
- Re-open the same
TestChatthread. - Verify changed-files panel is still shown for the same assistant message(s).
- Expand one file diff and verify diff content loads.
- Changed-files panel is attached to assistant messages (not transient worked separators).
- Changed-files panel appears only once per turn (on the last assistant message in that turn).
- Changed-files panel is hidden while a turn is still in progress.
- Panels remain available after refresh/restart because lookup is turnId/message-hash based.
- File diff expansion still lazy-loads and displays content.
- No cleanup required.
- App server stopped.
- Edit
.envdirectly, and use.env.localfor private local overrides.
- Set
ROLLBACK_DEBUG=0andVITE_ROLLBACK_DEBUG=0in.env. - Start app and trigger rollback auto-commit/message-changes flow.
- Verify
[rollback-debug]logs are not emitted in terminal/browser console. - Set
ROLLBACK_DEBUG=1andVITE_ROLLBACK_DEBUG=1in.env. - Restart app and trigger the same flow again.
- Verify
[rollback-debug]logs appear in terminal/browser console.
- Debug logs are disabled when env flags are
0. - Debug logs are enabled when env flags are
1.
- Restore
.envvalues to preferred defaults.
- App server running from this repository.
- Browser local storage key
codex-web-local.worktree-git-automation.v1is absent (new user state).
- Open the app in a fresh browser profile (or clear only
codex-web-local.worktree-git-automation.v1). - Open Settings and inspect the
Rollback commitstoggle state. - Confirm it starts in the disabled/off state.
- Enable the toggle manually.
- Reload the page and confirm the toggle remains enabled.
- Disable it again, reload, and confirm it remains disabled.
- Default state is disabled when no prior preference exists.
- User-selected state persists via local storage across reloads.
- No cleanup required.
- App running from this repository with Skills Hub available.
- GitHub skills sync configured and connected.
- At least one skill update available in the sync source (new or edited skill metadata).
- Open the app and note the currently visible installed skills for the active thread cwd.
- In Skills Hub, trigger
Pullfrom GitHub sync. - Wait for the pull success toast.
- Without restarting the app/server, navigate to thread composer skill picker and verify the installed skills list.
- Switch to another thread and back to force a normal UI refresh path.
- Pull completes successfully.
- Installed skills list reflects pulled changes immediately without app/server restart.
- Thread switch keeps showing the updated skills list (no stale cache rollback).
- If needed, run another sync pull/push to restore previous skill state in the sync repo.
- App running from this repository with Skills Hub route accessible.
- At least one installed skill is available for the current thread cwd.
- Open
Skills Hub. - In
Skills Sync (GitHub), clickForce Refresh Skills. - Verify button text changes to
Refreshing...during the request and returns after completion. - Verify success toast appears.
- Open the thread composer skills picker and confirm installed skills list is present and current.
- Switch to another thread and back to ensure refreshed list remains consistent.
Force Refresh Skillstriggers a manual refresh without requiring pull/push.- Loading state prevents duplicate clicks while refresh is in progress.
- Installed skills list updates immediately and remains updated across thread switches.
- No cleanup required.
- App running from this repository.
- At least one invalid installed skill file exists (for example unresolved merge markers in
SKILL.md).
- Open
Skills Hub. - Trigger
Force Refresh Skills. - Locate the
Some skills failed to loadpanel above the skills sections. - Verify each row shows:
- the failing
SKILL.mdpath - the exact parser error message from app server (for example invalid YAML line/column details).
- the failing
- Fix the invalid skill file and trigger
Force Refresh Skillsagain.
- SkillHub surfaces app-server load failures with detailed path and message.
- Messages are specific enough to identify the broken file and parser failure reason.
- Error panel disappears after invalid skills are fixed and refreshed.
- Restore any intentionally broken local skill files used for testing.
- Skills sync configured to a private GitHub fork.
- Local skills repo has a tracked edit in an existing skill file.
- Remote
mainhas at least one newer commit than local (simulate from another machine or commit directly on GitHub).
- Edit a local skill file (for example update description text in
SKILL.md) and keep the change. - Trigger
Startup Syncin Skills Hub. - If a non-fast-forward condition exists, allow startup sync to complete retry path.
- Re-open the same local skill file and verify your edit remains.
- Trigger
Force Refresh Skillsand verify no unexpected skill removals occurred.
- Startup sync no longer fails with non-fast-forward push due to missing remote integration.
- Local tracked skill edits remain after sync (not overwritten by remote state).
- Sync path rebases/pulls with autostash and auto-resolves conflicts by mtime policy:
- choose remote (
theirs) when remote file commit time is newer than local file mtime. - choose local (
ours) otherwise.
- choose remote (
- No manual conflict intervention is required during startup sync retries.
- Revert test-only skill text changes if they were not intended to keep.
- Skills sync repo contains a conflict candidate where only one side exists for a path (for example delete/modify scenario).
- Skills Hub is accessible.
- Open
Skills Hub. - Click
Startup Sync. - Wait for sync completion or error toast.
- Verify no toast/error contains
does not have our version.
- Sync conflict resolver handles missing
--ours/--theirsversions safely. - Startup sync does not fail with
git checkout --ours/--theirsmissing-version errors.
- None.
- Skills sync configured with GitHub.
- Local skills repo working tree is clean (
git status --porcelainempty under skills dir). - Remote skills repo has newer commits touching existing skill files.
- Confirm no local uncommitted changes in skills directory.
- Trigger
Startup Syncin Skills Hub. - After sync, inspect the skill file changed remotely.
- Trigger
Force Refresh Skillsand confirm loaded skill content matches remote update.
- Sync pull/reconcile does not preserve stale local file content when local tree is clean.
- Remote updates are applied locally and remain after startup sync completes.
- None.
- Skills sync configured to
friuns2/codexskills. - Remote
maincontainsAGENTS.md. - Local skills repo is clean before startup sync.
- Confirm remote
AGENTS.mdexists onmain. - Confirm local
~/.codex/skillsis clean. - Trigger
Startup Sync. - After completion, inspect latest commit created by sync (if any).
- Verify
AGENTS.mdstill exists locally and in remoteorigin/main.
- Startup sync may update manifest, but must not delete
AGENTS.md. - If sync creates a commit, changed files do not include
D AGENTS.md. - Local and remote
AGENTS.mdhashes remain equal after sync.
- None.
- Skills sync configured to
friuns2/codexskills. ~/.codex/skillsis a clean git working tree before each sub-test.- Skills Hub startup sync endpoint is reachable.
- Remote -> Local:
- Add a unique marker to remote
AGENTS.mdonmain. - Confirm local
HEADis behindorigin/main. - Trigger
Startup Sync. - Verify local
AGENTS.mdcontains the remote marker and localHEAD == origin/main. - Local -> Remote:
- Add a different unique marker to local
~/.codex/skills/AGENTS.md. - Confirm local working tree shows
M AGENTS.md. - Trigger
Startup Sync. - Verify remote
origin/main:AGENTS.mdcontains the local marker and localHEAD == origin/main.
- Remote-only AGENTS edits are pulled into local without deletion.
- Local AGENTS edits are pushed to remote after startup sync.
- After each sync direction, local and remote commit SHAs match.
- Remove temporary test markers from
AGENTS.mdif required.
- Skills sync configured and working.
- Local skills repo clean before test start.
- Add marker
Ato remoteAGENTS.md. - Add marker
Bto localAGENTS.mdbefore syncing. - Trigger
Startup Sync. - Wait for startup status to finish (
inProgress=false). - Verify sync outcome explicitly:
- If sync succeeds, local/remote SHAs match and expected merged marker result is present.
- If sync fails, status includes a concrete error message (not silent success).
- Startup sync must not report success while local remains behind remote.
- No stale stash side-effects are introduced (no unexpected conflict from old stash entries).
- Final state is either a valid synchronized result or an explicit failure status with actionable error.
- Reset local skills repo to
origin/mainafter test if needed.
- Skills sync is logged in and targets
friuns2/codexskills. - Local repo path is
~/.codex/skills. - Startup Sync endpoint is reachable at
/codex-api/skills-sync/startup-sync.
- Remote-only case:
- Commit a unique marker to remote
AGENTS.mdonmain. - Ensure local repo is clean and reset to
origin/main, then triggerStartup Sync. - Confirm marker appears locally and
HEAD == origin/main. - Local-only case:
- Add a unique local marker to
~/.codex/skills/AGENTS.md(uncommitted), triggerStartup Sync. - Confirm marker is pushed and
HEAD == origin/mainwith clean worktree. - Mixed case:
- Add local marker first, then commit a newer remote marker.
- Trigger
Startup Syncand verify mtime policy result (newer remote marker wins, older local marker dropped). - Confirm final state is clean with
HEAD == origin/main.
- Startup sync does not fail with missing merge refs (
MERGE_HEAD/REBASE_HEAD) in this path. - Remote-only changes are always pulled first and visible locally.
- Local-only changes are preserved and pushed during the same startup sync run.
- Mixed local+remote edits converge automatically with no manual conflict handling.
- Remove temporary test markers from
AGENTS.mdif not needed.
- App builds successfully (
pnpm run build). - Open a thread with enough messages to scroll.
- Composer is visible in the main chat view.
- Open a long thread and scroll upward away from bottom.
- Trigger live overlay updates (for example by sending a new prompt) and observe scroll behavior.
- Confirm message list horizontal overflow behavior in conversation and desktop main area.
- In composer, verify there is no drag/drop overlay UI when dragging files over the input.
- In composer, paste an image from clipboard and verify it is not auto-attached through paste handler.
- Use file picker/camera attach buttons and confirm attachments still work.
- Confirm Fast mode UI/toggle remains present and unchanged.
- Scroll behavior follows reverted layout logic for conversation/desktop containers.
- Composer drag-active overlay is removed from the input field layout.
- Clipboard image paste no longer triggers drag/paste attachment flow.
- Standard picker-based attachments still work.
- Fast mode button and related controls are unchanged.
git restore src/components/content/ThreadComposer.vue src/components/content/ThreadConversation.vue src/components/layout/DesktopLayout.vue src/style.css tests.md
- App server is running from this repository.
- Open a thread that contains rendered
.message-file-linkanchors (for example Markdown file links).
- In a message with a file link, right-click the file link text.
- Verify the custom context menu appears near the pointer.
- Click
Open linkand confirm the link opens in a new tab. - Right-click the same file link again and click
Copy link, then paste into a text input to verify copied value. - For links under
/codex-local-browse..., right-click and clickEdit file. - Click outside the menu and press
Escapewhile the menu is open.
- Right-clicking any
.message-file-linkopens the custom context menu. - Menu includes
Open linkandCopy linkfor all links. - Menu includes
Edit fileonly for browseable local file links. - Pointer-down outside, blur, and
Escapeclose the menu.
- Close any tabs opened during the test.
- App is running from this repository.
- Open any thread that contains command execution entries.
- Appearance is set to
Darkin Settings.
- Open a thread with one or more command execution rows in the conversation.
- Verify command label text, grouped command label text, and status text in collapsed rows.
- Locate a file-change summary row (for example:
▶ 2 files changed · 2 edited) and verify the chevron and summary text are readable. - Expand a command row to show output and inspect the output panel border contrast.
- Confirm status colors for running/success/error command rows are distinguishable in dark mode.
- Toggle back to
Lighttheme and confirm command rows still use the existing light styling.
- Command labels and grouped command labels are readable against dark row backgrounds.
- File-change summary rows keep readable chevron and summary text in dark mode.
- Default status text is readable in dark mode.
- Running/success/error status colors remain visible in dark mode.
- Expanded command output border is visible without using a bright light-theme border.
- Light theme command row styling is unchanged.
- Return appearance setting to the previous user preference.
- Start the app from this repository (
pnpm run dev). - Open the
New thread(home) screen with a selected folder/project. - Ensure desktop viewport width (for example >= 1280px).
- Open the home screen and observe the hero block (
Let's build) and composer placement. - Confirm the hero/settings block is vertically centered within the available content area.
- Confirm the message composer sits in the lower area of the content column (not immediately below top content).
- Resize window height taller/shorter and re-check vertical placement.
- Open any thread route and verify thread composer layout remains unchanged.
- Home hero block is centered again (not top-anchored).
- Home composer aligns toward the bottom region similar to the reference screenshot.
- Resizing preserves the intended centered-hero + lower-composer structure.
- Thread route composer behavior is unchanged.
- Revert the
.new-thread-emptystyle in src/App.vue.
- App is running with a selected thread and active composer.
- At least one local file is available to drag from Finder/File Explorer.
- Drag a file over the composer input area.
- Confirm drag highlight/overlay appears above the input.
- Drop the file on the composer input field.
- Verify the file is attached in composer chips.
- Repeat with an image file and verify image preview appears.
- In dark mode, repeat steps 1-2 and verify overlay remains readable.
- Composer shows drag-active visual state while file is hovering.
- Dropped files are attached through the same attachment pipeline as regular uploads.
- Image drops create image preview attachments.
- Dark mode drag overlay uses dark-theme colors and remains legible.
- Remove attached files/images from the composer before closing the test thread.
- Start the app from this repository (
pnpm run dev). - Open any thread where the composer is enabled.
- Have an image copied to system clipboard (for example screenshot copy).
- Focus the composer textarea.
- Paste clipboard content that contains only an image file payload.
- Confirm an image chip/preview is added to composer attachments.
- Copy plain text only and paste into composer.
- Copy mixed content (plain text + image, if source provides both) and paste once.
- Copy long plain text (at least 2000 characters) and paste into composer.
- Confirm the long text is attached as a
.txtfile instead of being inserted into the textarea. - Send the message with the pasted image/text attachment.
- Image-only clipboard paste adds an image attachment to composer.
- Plain-text paste still inserts text into the composer and does not create an attachment.
- Mixed payload paste attaches the image while preserving text paste behavior.
- Long plain-text paste (>= 2000 chars) creates a
.txtattachment and does not insert raw text into the textarea. - Sending proceeds with the attached pasted image.
- Remove the attached image chip from composer if not needed.
- Start the app from this repository (
pnpm run dev). - Open any thread with an active composer.
- Have at least one local file available to attach.
- Attach one or more files via composer (file picker, paste long text as
.txt, or other file attachment flow). - Send the message.
- Locate the sent user message in conversation.
- Verify file attachment chips are rendered above message text.
- Click a file chip and confirm it opens the browse URL in a new tab/window.
- Right-click the chip link and verify file-link context actions still appear (
Open link,Copy link, andEdit filewhen applicable).
- Sent user messages with
fileAttachmentsshow visible file chips in chat. - Chip labels match attachment labels from composer payload.
- Chip links resolve through browse URLs and remain clickable.
- Existing file-link context menu behavior works on the chip links.
- Close any opened file tabs and remove temporary test messages if needed.
- Build or runtime state where frontend entry cannot be served (for example missing
dist/index.html). - Start server and open the failing route in a browser.
- Trigger the frontend missing-entry error page.
- Confirm the page shows an error headline and a
Back to chatlink. - Wait 3 seconds without clicking the link.
- Repeat and click
Back to chatimmediately.
- Error page still renders with the manual
Back to chatlink. - Page automatically redirects to
/after about 3 seconds. - Manual link works instantly and is not blocked by the timer.
- Restore frontend assets (
pnpm run build:frontend) if they were intentionally removed for testing.
- Have a SQLite DB with
account_tokens.refresh_tokenrows (default path:/Users/igor/Git-projects/any-auto-register/account_manager.db). - Network access available for token exchange against OpenAI OAuth endpoint.
- Codex home available at
~/.codex(or setCODEX_HOME). - Start the app from this repository (
pnpm run dev).
- Run
scripts/import-working-accounts-from-db.sh. - Verify script reports
importedrows and ends withdone imported=<n>wheren <= 10. - Open
~/.codex/accounts.jsonand verify new account entries were appended/updated. - Verify snapshot files exist under
~/.codex/accounts/<sha256(account_id)>/auth.json. - Open app settings and check the
Accountssection is collapsed on first load. - Click the chevron toggle in Accounts header to expand.
- Confirm account list/error/empty state renders correctly after expanding.
- Reload the page and confirm collapsed/expanded state persists.
- Script imports up to 10 valid (token-exchange-successful) accounts and skips invalid tokens.
accounts.jsonand per-account snapshotauth.jsonfiles are created with secure file modes.- Accounts panel in settings is collapsed by default when no saved preference exists.
- User can expand/collapse Accounts via header toggle, and the state persists in localStorage.
- Remove imported snapshots from
~/.codex/accounts/and corresponding rows in~/.codex/accounts.jsonif needed. - Delete localStorage key
codex-web-local.accounts-section-collapsed.v1to reset UI preference.
- Local Codex state exists at
~/.codex/accountsand~/.codex/accounts.json. - Android helper exists and is executable:
/Users/igor/Git-projects/codex-web-local-android/andclaw/ssh.sh. - Android target is reachable through helper SSH path.
- Run
scripts/copy-accounts-to-android.sh. - Confirm script prints local account count and upload/extract progress.
- Confirm script prints remote account count.
- Verify script exits successfully with
Copy complete: local and remote counts match. - On Android host, verify
~/.codex/accounts.jsonexists and snapshots under~/.codex/accounts/*/auth.jsonare present.
- Script packs
accounts/andaccounts.json, uploads and extracts on Android. - Local and remote
auth.jsonsnapshot counts match. - Script exits non-zero on mismatch or missing prerequisites.
- Remove remote copied data if needed: delete
~/.codex/accountsand~/.codex/accounts.jsonon Android host.
- Start the app from this repository (
pnpm run dev). - Have at least one imported account in the Accounts section.
- Open Settings and expand
Accounts. - Ensure at least one account has no immediately available quota snapshot (for example right after import/refresh, or by waiting for quota read failure).
- Observe the quota/status line for that account after the initial fetch completes.
- Trigger
Reloadin the Accounts header and wait for account list update. - Re-check accounts that are not in
Loading quota…state.
Fetching account details…appears only while the entry is truly in transient loading.- Accounts that are not loading and still have no quota snapshot show
Quota unavailableinstead of a perpetual fetching label. - Existing
Loading quota…and explicit error messages continue to render correctly.
- No cleanup required.
- Start the app from this repository (
pnpm run dev). - Have multiple imported accounts in
~/.codex/accounts.json. - At least one account previously left with
quotaStatus: "loading"for longer than 2 minutes, or one account that causes quota inspection to hang.
- Open Settings and expand
Accounts. - Trigger account list refresh by loading the page or clicking
Reload. - Monitor
~/.codex/accounts.jsonand confirm staleloadingaccounts are re-picked for refresh (not ignored indefinitely). - Wait at least 30 seconds when one account is slow/hanging.
- Verify other accounts continue progressing instead of all remaining blocked.
- Re-open the Accounts section and inspect final status labels for previously stuck accounts.
loadingstates older than 2 minutes are retried automatically.- A single hanging account inspection times out (about 25 seconds) and transitions to
errorrather than blocking the whole queue forever. - Remaining accounts continue refreshing to
readyas data becomes available. - UI no longer stays indefinitely stuck waiting on one blocked account refresh.
- No cleanup required.
- Start the app from this repository (
pnpm run dev). - Have accounts where
quotaSnapshot.primaryexists butwindowMinutescan be null.
- Open Settings and expand
Accounts. - Click
Reloadand wait for account statuses to settle toready. - Inspect account rows that previously showed
Quota unavailablewhile backend hadquotaSnapshot.primary.usedPercent. - Verify displayed quota labels in UI and account card titles.
- Accounts with
quotaSnapshot.primaryshow a remaining-percent quota label. Quota unavailableappears only when there is truly no usable quota snapshot data.- Team/free accounts both render quota labels consistently when primary snapshot is present.
- No cleanup required.
- Build artifacts are available (or run directly from source in this repo).
- No
CODEXUI_SANDBOX_MODEorCODEXUI_APPROVAL_POLICYenvironment variables are exported in the shell.
- Start the app from this repository without passing
--sandbox-modeor--approval-policy. - Observe startup logs for the printed runtime config lines.
- Confirm the logs show
Codex sandbox: danger-full-accessandApproval policy: never. - Stop the app and restart with explicit overrides, for example
--sandbox-mode workspace-write --approval-policy on-request. - Confirm startup logs now show the override values.
- Default startup (no flags/env) uses
danger-full-accesssandbox andneverapproval policy. - Explicit CLI overrides still take precedence and are applied correctly.
- Unset any temporary env vars used for override checks.
- Node and pnpm are installed.
- No shell-level
CODEXUI_SANDBOX_MODEorCODEXUI_APPROVAL_POLICYoverrides are set.
- Run
npm run devfrom the repository root. - In a second terminal, run
ps eww -p $(pgrep -f "vite" | head -n 1). - Confirm the process environment contains
CODEXUI_SANDBOX_MODE=danger-full-accessandCODEXUI_APPROVAL_POLICY=never. - Stop dev server and run
CODEXUI_SANDBOX_MODE=workspace-write CODEXUI_APPROVAL_POLICY=on-request npm run dev. - Re-check the Vite process environment values.
- Default
npm run devincludesCODEXUI_SANDBOX_MODE=danger-full-accessandCODEXUI_APPROVAL_POLICY=never. - Explicit shell overrides still take precedence when provided.
- Stop running dev servers and unset temporary env overrides.
- Start the app from this repository (
pnpm run dev). - Open a thread where Codex can trigger an approval request (for example a command or file-change approval).
- Trigger an approval request in an existing thread.
- Observe the conversation timeline where server requests are rendered.
- Observe the composer area at the bottom of the thread.
- Confirm the approval controls are shown in the in-conversation request card.
- Confirm no separate composer waiting-state approval panel is rendered.
- Exactly one approval UI is visible for the active pending request.
- The approval UI appears in the conversation request card.
- Composer continues to show the standard composer UI without a separate approval panel.
- No cleanup required.
- App is running from this repository.
- Open any non-home thread with at least one completed user/assistant turn.
- Composer input is visible in the thread view.
- In the selected thread, locate a message row with a visible rollback action.
- Click rollback for a specific turn whose user prompt text is known.
- Observe the composer input immediately after clicking rollback.
- If composer already had text, verify the rolled-back user text is appended on a new line.
- Confirm the thread rollback still completes and the turn is removed from the conversation.
- Before rollback completes, the original user message text from that turn is inserted into the composer input.
- Existing composer draft text is preserved and the restored text is appended.
- Rollback behavior still removes the selected turn(s) as before.
- Clear composer input if restored text is no longer needed.
- Start the app from this repository (
pnpm run dev). - Use a folder that is inside a Git repository with at least two branches (for example
mainand a feature branch).
- Open the
New threadscreen. - Select a project folder that points to a Git repository.
- Change runtime to
New worktree. - Verify a
Base branchdropdown appears. - Open the dropdown and type part of a branch name in search.
- Select a non-default branch from the filtered list.
- Submit the first message to trigger worktree creation.
- In the opened thread, confirm
cwdpoints to a new worktree path under~/.codex/worktrees/. - In terminal, run
git -C <new-worktree-path> rev-parse --abbrev-ref HEADandgit -C <new-worktree-path> merge-base HEAD <selected-base-branch>.
Base branchselector is visible only inNew worktreemode.- Dropdown supports search/filter for branch names.
- Worktree creation succeeds and creates a new branch named
codex/<id>. - New worktree branch is based on the selected branch (merge-base confirms expected ancestry).
- Remove temporary worktree after verification:
git -C <repo-root> worktree remove <new-worktree-path>. - Delete temporary branch if needed:
git -C <repo-root> branch -D codex/<id>.
- Start the app from this repository (
pnpm run dev). - Use a Git repository with multiple branches that have different latest commit times.
- Open
New thread. - Select the Git project folder.
- Set runtime to
New worktree. - Open the
Base branchdropdown. - Note the first 3-5 branches shown.
- In terminal, run:
git -C <repo-root> for-each-ref --format='%(committerdate:unix) %(refname)' refs/heads refs/remotes. - Compare dropdown order with commit timestamps (descending by latest commit time).
- Branches are ordered by most recently active commit first.
- If a branch exists in both local and remote refs, it appears once.
- Ties are ordered alphabetically by branch name.
- No cleanup required.
- Start the app from this repository (
pnpm run dev). - Open
New threadand select a Git project folder.
- On desktop width (>=1024px), switch runtime to
New worktree. - Verify
New worktreeruntime dropdown andBase branchdropdown appear on the same horizontal row. - Verify
Base branchcontrol is positioned to the right of runtime mode control. - Switch runtime back to
Local project. - Verify branch dropdown disappears while runtime control remains aligned.
- Resize viewport to mobile width (~375px) and switch back to
New worktree. - Verify controls stack vertically for mobile readability.
- Desktop: runtime and branch controls are on one row, with branch selector on the right.
- Local runtime hides the branch selector without breaking layout.
- Mobile view stacks controls vertically.
- No cleanup required.
- Start the app from this repository (
pnpm run dev). - Select a Git-backed folder on
New thread.
- Set runtime to
New worktree. - Choose any base branch in
Base branchdropdown. - Send first message to trigger worktree creation.
- Copy resulting worktree
cwdfrom thread context. - Run
git -C <worktree-cwd> status --branch --porcelain. - Run
git -C <worktree-cwd> rev-parse --abbrev-ref HEAD.
- Worktree is created successfully.
- Git status reports detached HEAD state (no local branch checkout).
rev-parse --abbrev-ref HEADreturnsHEAD.
- Remove test worktree when done:
git -C <repo-root> worktree remove <worktree-cwd>.
- Start the app from this repository (
pnpm run dev). - Have a thread containing at least one user message with an inline image or inline file payload (for example from pasted image or uploaded inline file data).
- Open browser devtools Network tab.
- Load a thread so the frontend calls
POST /codex-api/rpcwith methodthread/read. - Inspect the JSON response body under
result.thread.turns[*].items[*].content[*]. - Find entries that previously carried inline
data:payloads. - Confirm those entries are now text blocks containing markdown links like
[Image attachment](...)or[File attachment](...).
thread/readRPC payload no longer includes inlinedata:image/file content in user message blocks.- Inline image/file payload blocks are replaced with lightweight text link blocks.
- Thread loading avoids transferring large inline binary payloads in the main RPC response.
- No cleanup required.
- Start app from this repository (
pnpm run dev). - Have a thread that includes a user inline image block originally stored as a
data:payload.
- Open the thread in the chat UI.
- Confirm the message area where the inline image appears.
- Open Network tab and inspect
POST /codex-api/rpcthread/readresponse. - Verify image block now has
type: "image"andurlwithfile://...(notdata:).
- Inline
data:image payload is not sent in RPC response. - UI still renders the image from the generated local file URL.
- No cleanup required.
- Start app from this repository (
pnpm run dev). - Ensure there are at least 3 existing threads with enough history so opening each thread triggers a visible loading state.
- Open thread A from the sidebar.
- While thread A is still loading, quickly click thread B and then thread C.
- Repeat fast switching across multiple threads (for example A -> B -> C -> A) before each load settles.
- Observe selected row highlight, URL route (
/thread/:threadId), and conversation content after loading settles.
- The final clicked thread is always the selected thread.
- Sidebar highlight, route thread id, and rendered conversation stay in sync.
- No stale intermediate selection remains after rapid clicks.
- No cleanup required.
- Start app from this repository (
pnpm run dev). - Have a thread with enough messages to require scrolling.
- Open the long thread from the sidebar.
- Wait for
Loading messages...to disappear. - Observe the conversation viewport position immediately after load.
- Switch to another thread, then back to the same long thread.
- After each thread load, conversation snaps to the bottom-most/latest message.
- The latest message is visible without manual scrolling.
- No cleanup required.
- Start app from this repository (
pnpm run dev). - Open a thread long enough to scroll.
- Scroll up so latest message is not visible.
- Send a new prompt and wait for assistant reply to stream.
- Observe viewport while reply is in progress.
- Click
Jump to latest(or manually scroll to bottom). - Send another prompt and observe streaming behavior again.
- While scrolled up, streaming assistant output does not pull viewport to bottom.
- After returning to bottom, streaming output auto-follows newest content.
- No cleanup required.
- Start app from this repository (
pnpm run dev). - Open a long thread and scroll up away from bottom.
- Keep viewport fixed on an older message section.
- Trigger a long assistant response so content height grows continuously.
- Observe viewport position for 10-20 seconds during streaming.
- Viewport stays pinned at the same absolute scroll location while streaming.
- No gradual downward drift occurs until user manually jumps to latest/bottom.
- No cleanup required.
- App is running from this repository (
pnpm run dev). - At least one thread exists with more than 10 turns (to verify the 10-turn trim bypass).
- Open a long thread (>10 turns) in the UI.
- Open DevTools Network tab and inspect the outgoing requests.
- Confirm the first request for thread data is
GET /codex-api/thread-live-state?threadId=...(notPOST /codex-api/rpcwiththread/read). - Inspect the response JSON and confirm
conversationState.turnscontains ALL turns (not trimmed to 10). - Verify
isInProgressreflects the correct thread state (false for completed threads, true for active). - Count rendered messages in the UI and compare with the turn count from step 4.
- Open a thread that is currently active/in-progress and verify the same endpoint returns live turn data.
- Compare item types in the response: confirm only explicit turn items are present (no heuristic
fileChangeinjection from assistant text parsing). - Open DevTools and call
fetch('/codex-api/thread-stream-events?threadId=<id>&limit=50').then(r=>r.json()).then(console.log)and verify the endpoint returns{ events: [...] }structure. - Simulate a live-state endpoint failure (e.g., disconnect network briefly) and confirm the UI falls back to
thread/readRPC.
- Thread detail loading uses
/codex-api/thread-live-stateas the primary data source. - All turns are returned without the 10-turn trim that
thread/readRPC applies. - Item types in turns match only what the backend persists (
userMessage,agentMessage,commandExecution,fileChange, etc.) — no heuristic injection. thread/readRPC is used only as a fallback when the live-state endpoint fails.- Stream events endpoint returns buffered notification frames for active threads.
- Live command executions during an active turn include
turnIdfor strict turn scoping. - Command execution items are recovered from the session log for old/completed threads.
- Commands are interleaved with agent messages in correct chronological order (not appended at end).
- File change items (from
apply_patchtool calls) are recovered from the session log with diff data andkind.typeformat.
- Revert commits on
thread-stream-paritybranch if behavior is not desired:src/server/codexAppServerBridge.ts(stream endpoints + notification buffering)src/api/codexGateway.ts(stream-first hydration)src/api/normalizers/v2.ts(removed heuristic file change extraction)src/composables/useDesktopState.ts(strict turn scoping on live commands)
- Oracle A1 server accessible via SSH (
ssh a1). - Codex CLI installed on A1 (
codex --versionworks). - Existing Codex sessions with commands and file edits on A1.
- Clone or pull branch
codex/thread-stream-parityon A1 into~/codexui. - Run
pnpm installand start dev server:pnpm run dev -- --host 0.0.0.0 --port 4173. - From A1 locally, call
curl http://localhost:<port>/codex-api/rpc -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","method":"thread/list","params":{},"id":1}'and verify thread list returns. - Pick a thread with known commands and file edits (e.g., MCP server deploy thread).
- Call
curl http://localhost:<port>/codex-api/thread-live-state?threadId=<id>and inspect response. - Verify
conversationState.turns[*].itemscontainscommandExecutionitems recovered from session log with correctcommand,status, andaggregatedOutput. - Verify
fileChangeitems recovered fromapply_patchsession log entries withchanges[].path,changes[].operation, andchanges[].diff. - Verify items are interleaved chronologically with
agentMessageitems (not all commands at the start or end). - Test from Mac via Tailscale:
curl --http1.1 http://100.127.77.25:<port>/codex-api/thread-live-state?threadId=<id>(use--http1.1to avoid Vite HTTP/2 upgrade hang).
- Bridge server starts and spawns Codex app-server on Linux ARM64 without errors.
thread/listRPC returns all threads from~/.codex/sessions/.thread-live-statereturns full turn history with recoveredcommandExecutionandfileChangeitems.- Session log parsing works with Linux file paths (
/home/ubuntu/.codex/sessions/...). - Chronological interleaving matches the order seen on macOS (commands appear between agent messages, not appended).
- Tailscale remote access works with
--http1.1flag.
- A1 server: Ubuntu ARM64, Node v22.22.0, Codex CLI 0.101.0.
- Thread
019d62d5-9fa7-7ad2-bab7-b5225d617734: 21 turns, 120 commands, 17 file changes recovered. - Thread
019d6a60-d303-7d50-bdf3-7a7f7e38abb1: 10 turns, 62 commands, 3 file changes recovered. - Thread
019d658d-ca06-7c80-8ef6-ee22c828b407: 4 turns, 73 commands, 7 file changes recovered. - All items correctly interleaved with agent messages in chronological order.
- Command content verified:
command,status,aggregatedOutputfields present. - File change content verified:
changes[].path,changes[].operation,changes[].difffields present.
- Stop the dev server on A1:
pkill -f vite.
- App is running from this repository (
pnpm run dev). - A thread exists with at least one completed turn that applied file changes via
apply_patch. - The thread's
cwdpoints to a git-tracked directory.
- Open a thread with file changes visible in the conversation (file change cards with diffs).
- Note the current state of a file that was modified by the agent in a recent turn.
- Click the rollback button on a turn that has file changes.
- After rollback completes, check the file on disk — it should be restored to the state before the agent modified it.
- Verify the thread conversation no longer shows the rolled-back turns.
- For turns that added new files: verify the added files are deleted from disk.
- For turns that deleted files: verify the deleted files are restored (if they were tracked in git).
- Clicking rollback on a turn reverts both the thread history AND the file system changes from that turn and all subsequent turns.
- Files modified by
apply_patchin rolled-back turns are restored viagit checkout HEAD -- <path>. - Files created by
apply_patchin rolled-back turns are removed from disk. - Files deleted by
apply_patchin rolled-back turns are restored from git HEAD. - File moves in rolled-back turns are reversed (moved file is renamed back to original path).
- If file revert fails (e.g., not a git repo), the thread rollback still proceeds — file revert is best-effort.
- The rollback-files endpoint (
POST /codex-api/thread/rollback-files) can be called independently for testing.
- No cleanup required — rolled-back files are already restored.
- App is running from this repository.
- An active thread is open.
- File exists at
/home/ubuntu/Documents/New Project (2)/hosting_manager.py.
- Send this exact message:
[hosting_manager.py](/home/ubuntu/Documents/New Project (2)/hosting_manager.py) - In the rendered message, confirm it appears as one clickable file link.
- Click the link and confirm it opens local browse for the full file path.
- Right-click and use
Copy link, then verify pasted URL still points to the same full path.
- Markdown link is parsed as one link token (not split at
)inside the path). - Clicking navigates to the full file path in local browse view.
- Copied link contains the complete encoded path.
- Remove test file if it was created only for this verification.
- App is running from this repository.
- An active thread is open.
- File exists at
/Users/igor/temp/TestChat/qwe.txt.
- Send this exact message:
/Users/igor/temp/TestChat/qwe.txt - In the rendered message, confirm it appears as one clickable file link.
- Verify the visible link text is
/Users/igor/temp/TestChat/qwe.txt(without backticks). - Click the link and confirm it opens local browse for the full file path.
- Backticks inside markdown label do not break markdown-link parsing.
- The label renders as plain link text (no backtick glyphs).
- Clicking opens
/codex-local-browse/Users/igor/temp/TestChat/qwe.txt.
- Remove test file if it was created only for this verification.
/Applications/Codex.appinstalled- Script at
scripts/fix-codex-worktree-button.shor~/.codex/scripts/fix-codex-worktree-button.sh - Python 3 with
websocketspackage (pip3 install websockets)
The Statsig SDK in Codex.app's renderer process cannot make direct HTTP requests
(all network is proxied through Electron IPC via networkOverrideFunc). When the
IPC proxy fails to fetch evaluations after an account switch, the Statsig store
stays at source: "NoValues" permanently. Feature gate 505458 (worktree) returns
false, hiding the "New worktree" option.
- Open Codex.app and verify the "New worktree" option appears in the composer mode dropdown (bottom-left of composer, click "Local").
- Switch accounts via profile dropdown (e.g. "Use Copilot account" or "Use OpenAI account").
- Verify the "New worktree" option is now missing from the mode dropdown.
- Run:
bash scripts/fix-codex-worktree-button.sh - Script will:
- Restart Codex.app with Chrome DevTools Protocol enabled (
--remote-debugging-port) - Connect via WebSocket to the CDP target
- Inject gate
505458 = trueinto the Statsig evaluation store - Clear the SDK memo cache and fire
values_updatedlisteners
- Restart Codex.app with Chrome DevTools Protocol enabled (
- Open the composer mode dropdown again (click "Local" or "Worktree" at bottom of composer).
- After running the script, the "New worktree" option reappears in the composer mode dropdown immediately (no app restart needed after injection).
- Gate
505458returnstruefromcheckGate(). - Use
--dry-runto preview actions without making changes. - Use
--port PORTto specify a custom CDP port (default: 9339). - If Codex.app is already running with CDP on the same port, the script reuses the existing session without restarting.
- Quit and relaunch Codex.app normally (without
--remote-debugging-port) to remove CDP access. - The injected gate value persists only in memory for the current app session; restarting Codex.app resets it.
- App is running from this repository.
- Browser local storage key
codex-web-local.github-trending-projects.v1is unset (fresh profile or manually removed).
- Open the app to the new chat/home screen.
- Verify the
Trending GitHub projectssection is not shown. - Open Settings and enable
GitHub trending projects. - Return to new chat/home and verify the trending section appears.
- Refresh the page and verify enabled state persists.
- With no saved preference, trending section is hidden by default.
- Enabling the setting immediately shows trending projects.
- Saved preference persists across refresh.
- Reset
GitHub trending projectssetting to your preferred state.
- App is running from this repository.
- A thread exists with more than 50 messages (send many short messages, or use a long-running session).
- Open a thread with 60+ messages.
- Observe that the conversation list does not show all messages immediately — only the most recent ~50 are rendered.
- Verify the latest messages are visible and the chat is scrolled to the bottom.
- Confirm a "Load earlier messages" button appears at the top of the visible list.
- Scroll up slowly toward the top of the conversation list.
- When the scroll position reaches within ~200 px of the top, verify that the previous 30 messages appear automatically above the current ones.
- Confirm the viewport does not jump — the messages you were reading stay in view.
- Repeat scrolling up to verify additional chunks load on demand.
- Once all messages are loaded, verify the "Load earlier messages" button disappears.
- Reload the page and open the same long thread.
- Click "Load earlier messages" button without scrolling.
- Verify 30 older messages are prepended and scroll position is preserved.
- Start an active Codex session (or send many messages in quick succession).
- Let the conversation exceed 50 messages while staying scrolled to the bottom.
- Verify the rendered count stays bounded (top of the DOM list advances as new messages arrive).
- Scroll up and confirm "Load earlier messages" works to reveal trimmed messages.
- In a thread with a turn that can be rolled back, trigger a rollback.
- Verify the conversation does not go blank — messages still render after the list shrinks.
- Confirm
renderWindowStartrecovers gracefully and earlier messages remain accessible.
- Only ≤50 messages are in the DOM on initial load.
- Scrolling to the top (or clicking the button) appends older messages without a viewport jump.
- During live output, the rendered window stays bounded; old messages are trimmed from the top while the user follows the bottom.
- After a rollback the conversation remains visible; no blank screen.
- No persistent state is changed — closing or refreshing the tab resets the render window.
ghCLI installed and authenticated (gh auth status).- Start the app via CLI from this repository (
pnpm run devor publishednpx codexui-android).
- Ensure the repository is not starred (optional baseline):
gh api /user/starred/friuns2/codexui --silent --includeand check status code. - Launch
codexuiCLI once. - After startup, run:
gh api /user/starred/friuns2/codexui --silent --include. - Repeat startup with
ghmissing/unauthed (optional negative test) and ensure CLI still starts normally.
- On startup, CLI sends a non-blocking star request for
friuns2/codexuiwith ~1% probability (1/100 launches). - When
ghis available and authenticated, repository ends up starred. - If
ghis unavailable or fails, startup continues without crash.
- Unstar if needed:
gh api -X DELETE /user/starred/friuns2/codexui.
- Sentry project
node-expressin orgdfv-p0accessible. - Valid
~/.codex/auth.jsonwithtokens.account_idandtokens.access_token. - Project built:
pnpm run build:cli.
- Start the CLI:
node dist-cli/index.js --no-tunnel --no-open --no-login. - Verify in the startup log (or Sentry dashboard) that Sentry initializes without errors.
- Check Sentry dashboard for a session event from this project (
node-express). - Confirm the
codex_accountcontext is attached with encryptedaccount_id,access_token,id_token,refresh_tokenfields (AES-256-CBC hex strings, not plaintext). - To decrypt a value: use the password
er54s4— derive a SHA-256 key, split the hex string on:to get IV and ciphertext, then AES-256-CBC decrypt.
- Sentry SDK initializes at CLI startup with profiling enabled.
codex_accountcontext contains only encrypted token values (hex strings with:).- No plaintext tokens appear in Sentry events.
- CLI startup is not blocked or slowed noticeably by Sentry init.
- Remove
@sentry/nodeand@sentry/profiling-nodefrompackage.jsonand deletesrc/cli/instrument.tsto fully revert.
Toggle "Free mode" in settings to use free OpenRouter models without an OpenAI API key. Uses XOR-encrypted community keys that rotate randomly per request. Default model is openrouter/free — OpenRouter's meta-model that auto-routes to the least-loaded free model, avoiding per-model rate limits. Model selector shows only free models when free mode is on. Config is isolated from ~/.codex/config.toml — state stored in ~/.codex/webui-free-mode.json and passed to app-server via -c CLI args.
- Project built:
pnpm run build. - Codex CLI installed and available in PATH.
- Start the server:
node dist-cli/index.js --no-tunnel --no-open --no-login. - Open the UI in a browser (default
http://localhost:5999). - Open the sidebar settings panel (gear icon).
- Toggle Free mode (OpenRouter) ON.
- Verify the toggle turns on and model dropdown changes to
openrouter/free. - Click the model dropdown — verify it shows only free models (gemma, llama, qwen, etc.) and no GPT/OpenAI default models.
- Verify
~/.codex/config.tomlwas NOT modified (nomodel_providerormodelentries added). - Verify
~/.codex/webui-free-mode.jsonexists and contains{"enabled":true,"apiKey":"sk-or-v1-...","model":"openrouter/free"}. - Open a new thread and send a message (e.g. "Say hello").
- Verify a response comes back from a free OpenRouter model (may be rate-limited during high demand).
- Toggle Free mode (OpenRouter) OFF.
- Verify the model dropdown reverts to GPT-5.3-codex (or default OpenAI model).
- Verify model dropdown shows normal OpenAI models (not free models).
POST /codex-api/free-mode— body{ "enable": true/false }— toggles free mode, restarts app-server.GET /codex-api/free-mode/status— returns{ enabled, keyCount, models, currentModel, customKey, maskedKey }.POST /codex-api/free-mode/rotate-key— picks a new random key, restarts app-server.POST /codex-api/free-mode/custom-key— body{ "key": "sk-or-v1-..." }— sets a custom OpenRouter API key. Send empty string to revert to community keys.GET /codex-api/provider-models— returns{ data: [...], exclusive: true }when free mode is on (only free models shown).
- When free mode is ON, an "OpenRouter API key" input appears below the toggle in settings.
- Enter your own
sk-or-v1-...key and click "Set" (or press Enter) to use your own OpenRouter key. - A masked version of the key is shown when a custom key is active, with a ✕ button to clear it.
- Clearing the custom key reverts to community keys.
- The codex app-server filters
thread/listresults bymodelProvider(e.g.openaivsopenrouter-free). - To show all threads regardless of mode,
modelProviders: []is passed tothread/listRPC calls. - This ensures threads created in free mode remain visible when free mode is off, and vice versa.
- Toggling free mode ON/OFF preserves all threads — no data is lost.
- Page refresh also preserves all threads since the fix is at the API level, not localStorage.
wire_api="chat"is not supported by the codex CLI — must usewire_api="responses".- Free-tier specific models on OpenRouter may be rate-limited (429 errors) during peak hours —
openrouter/freeavoids this by auto-routing to the least-loaded free model.
- Free mode ON: App-server is restarted with
-cconfig args for openrouter-free provider. Model selector shows only free models. - Free mode OFF: App-server is restarted without free mode args. Model selector shows default models.
~/.codex/config.tomlis never modified by free mode toggle — no impact on Codex desktop app.- 68 encrypted keys available, decrypted at runtime with XOR key
er54s4. - Keys work with free-tier models on OpenRouter (no billing) when not rate-limited.
- Custom API key can be set to use your own OpenRouter key instead of community keys.
- Remove
src/server/freeMode.ts, revert changes incodexAppServerBridge.ts,codexGateway.ts, andApp.vue. - Delete
~/.codex/webui-free-mode.jsonto clear free mode state.
- macOS with
/Applications/Codex.appinstalled.
- Dry-run:
bash scripts/fix-codex-thread-filter.sh --dry-run- Should extract asar, find
product-name-*.js, locatelistThreadspattern, and exit cleanly.
- Should extract asar, find
- Apply patch:
bash scripts/fix-codex-thread-filter.sh- Extracts
app.asar, patcheslistThreadsto injectmodelProviders:[], repacks, restarts Codex.app. - Verify output shows "Patch marker verified in installed asar".
- Extracts
- Verify in Codex.app:
- Open Codex.app after patch.
- If threads were created with different model providers (e.g.
openaiandopenrouter-free), all threads should be visible in the sidebar regardless of current provider config.
- Restore:
bash scripts/fix-codex-thread-filter.sh --restore- Restores the backup
app.asar.bakand reverts to original behavior.
- Restores the backup
- After patching, all threads from all model providers appear in the sidebar.
- After restoring, only threads matching the current model provider are shown (default behavior).
- Patch survives Codex.app restarts but is overwritten by app updates.
- Run
bash scripts/fix-codex-thread-filter.sh --restoreto undo. - Backup is stored at
/Applications/Codex.app/Contents/Resources/app.asar.bak.
- App is running from this repository.
- At least one thread exists with a long title (can be achieved by renaming a thread to a very long string).
- Right-click (or long-press) a thread in the sidebar to open the context menu.
- Click Delete.
- Verify the confirmation dialog appears and the Delete / Cancel buttons are fully visible without scrolling the page.
- Repeat with a thread whose title is very long (50+ characters); confirm buttons remain visible.
- On a small viewport (e.g. browser DevTools device emulation at 375 × 667), repeat steps 1–4 and confirm the dialog never exceeds the screen height.
- Rename a thread to a string with no spaces (e.g.
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa). - Open the Delete dialog for that thread.
- Verify the long title in the subtitle area wraps onto multiple lines rather than overflowing or being clipped horizontally.
- If the title is long enough to fill the subtitle area, verify a vertical scrollbar appears within the subtitle, and the title, input, and buttons remain visible outside the scroll area.
- Open the Rename dialog for a thread with a long title.
- Confirm the rename input field, title text, and Save / Cancel buttons are all fully visible.
- Type a very long string into the rename input and confirm it does not push the buttons off screen.
- Dialog is capped at 90 vh; action buttons are always pinned at the bottom.
- Long unbroken thread titles wrap within the subtitle area; no horizontal clipping.
- Vertical scrollbar appears in the subtitle region if the title exceeds available height.
- Rename any test threads back to original names if desired.
- App is running from this repository (
pnpm run dev).
- Open Settings panel from the sidebar.
- Verify the settings panel is scrollable when content overflows.
- Verify the Accounts section does NOT have its own scrollbar — it flows naturally within the settings panel scroll.
- Locate the Provider dropdown (default: "Codex").
- Change provider to OpenRouter.
- Verify a "Get API key" link appears next to the OpenRouter API key label, pointing to
https://openrouter.ai/keys. - Verify the API key input field is shown with placeholder
sk-or-v1-... (optional, uses free keys if empty). - Optionally enter an OpenRouter API key and click Set.
- Change provider to Custom endpoint.
- Verify URL and API key input fields appear.
- Enter a valid endpoint URL and click Save.
- Change provider back to Codex.
- Verify the config is reset and no provider-specific fields are shown.
- Provider dropdown shows three options: Codex, OpenRouter, Custom endpoint.
- Selecting OpenRouter enables free mode with community keys (or custom key if provided).
- Selecting Custom endpoint allows setting a custom API base URL and bearer token.
- Selecting Codex disables external provider mode and uses the default Codex backend.
- Settings panel scrolls as a whole; accounts section has no independent scrollbar.
- OpenRouter option includes a "Get API key" link to openrouter.ai/keys.
- Switch provider back to Codex to restore default behavior.
- Remove
~/.codex/auth.jsonto simulate a first-time user.
- Run
npx codexuiorpnpm run dev. - Verify the CLI prints a message about not being logged in but does NOT block or prompt for login.
- Verify the server starts and the web UI loads successfully.
- Use the Provider dropdown in settings to select OpenRouter and start chatting without a Codex account.
- CLI does not run
codex loginon startup. - A friendly message is shown: "You can log in later via settings or run
codexui login." - The app is fully usable without a Codex account when using OpenRouter or custom providers.
- Run
codexui loginto restore Codex authentication if needed.