Repository navigation
feat(gui): Codex Auth becomes Codex Set, with a prompt-layer panel #2679
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
10 commits
Select commit
Hold shift + click to select a range
c0ae9fd
feat(gui): Codex Auth becomes Codex Set, with a working Prompt panel
lidge-jun d3ed063
fix(gui): show a failed prompt read, and lazy-mount both Codex Set pa…
lidge-jun 5b2a9b9
test(gui): prove the stale-revision re-read is installed, not merely …
lidge-jun b7b6a58
feat(gui): render the whole prompt-layer taxonomy, with a read-only d…
lidge-jun b7fd788
fix(gui): make the layer-copy maps actually exhaustive, and finish th…
lidge-jun 33e3d4d
fix(gui): finish the unknown-layer fallback, and make case 11 watch t…
lidge-jun 57fbc24
docs(gui): add Codex Set prompt-panel screenshots for the stack
b006642
fix(gui): latch the lazy mounts and keep the resource key private
d47e618
fix(gui): route the features link, and declare the endpoint's CLI parity
ba77ff8
test(providers): follow the Add Provider CTA to its renamed route
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,117 @@ | ||
| import { useCallback, useEffect, useRef } from "react"; | ||
| import { useT } from "../../i18n/shared"; | ||
| import type { LayerDescriptorDto, ToggleStateDto } from "../../pages/codex-set-prompt"; | ||
| import { CLASS_LABEL_KEYS, LAYER_ABOUT_KEYS, LAYER_CONDITION_KEYS, LAYER_LABEL_KEYS } from "./prompt-layer-copy"; | ||
|
|
||
| /** | ||
| * Read-only detail for a built-in layer (ask item 8). | ||
| * | ||
| * There is no rendered prompt text here, and that is deliberate. Codex exposes | ||
| * no API for a layer's assembled body, and reconstructing one would mean | ||
| * reimplementing world_state.rs against a moving target. The dialog explains the | ||
| * layer, names its key, and shows this file's value - the honest scope. Saying so | ||
| * plainly beats an empty panel the reader has to interpret. | ||
| * | ||
| * No textarea, no Save. Escape closes and returns focus to the trigger. | ||
| */ | ||
| export default function PromptLayerDialog({ | ||
| descriptor, | ||
| toggle, | ||
| onClose, | ||
| }: { | ||
| descriptor: LayerDescriptorDto; | ||
| toggle: ToggleStateDto | undefined; | ||
| onClose: () => void; | ||
| }) { | ||
| const t = useT(); | ||
| const dialogRef = useRef<HTMLDialogElement>(null); | ||
| const titleId = "codex-set-layer-dialog-" + descriptor.id; | ||
|
|
||
| useEffect(() => { | ||
| const dialog = dialogRef.current; | ||
| // Remember who opened this. A modal that closes to the document root strands a | ||
| // keyboard user, and the row they were reading is the only sensible return. | ||
| const opener = document.activeElement as HTMLElement | null; | ||
| if (dialog && !dialog.open) dialog.showModal(); | ||
| // A parent re-render that drops this node while the dialog is open would | ||
| // otherwise leave the top layer occupied. | ||
| return () => { | ||
| if (dialog?.open) dialog.close(); | ||
| if (opener && typeof opener.focus === "function") opener.focus(); | ||
| }; | ||
| }, []); | ||
|
|
||
| const handleCancel = useCallback((event: React.SyntheticEvent) => { | ||
| // Escape arrives as `cancel`. Prevent the default close so React owns the | ||
| // unmount and the caller can restore focus to the row that opened this. | ||
| event.preventDefault(); | ||
| onClose(); | ||
| }, [onClose]); | ||
|
|
||
| const classKey = CLASS_LABEL_KEYS[descriptor.class]; | ||
| // Same fallback the row uses. The wire response is cast, not validated, so a | ||
| // newer runtime CAN send an id this build has no copy for - and a dialog whose | ||
| // title and body silently render blank is worse than one that admits the gap. | ||
| const labelKey = LAYER_LABEL_KEYS[descriptor.id]; | ||
| const aboutKey = LAYER_ABOUT_KEYS[descriptor.id]; | ||
| const conditionKey = LAYER_CONDITION_KEYS[descriptor.id]; | ||
|
|
||
| return ( | ||
| <dialog ref={dialogRef} className="modal-overlay" aria-labelledby={titleId} onCancel={handleCancel}> | ||
| <button type="button" className="modal-backdrop-dismiss" aria-label={t("common.close")} tabIndex={-1} onClick={onClose} /> | ||
| <div className="modal-card codex-set-layer-dialog" onClick={event => event.stopPropagation()} role="document"> | ||
| <div className="modal-head"> | ||
| <h3 id={titleId}>{labelKey ? t(labelKey) : descriptor.id}</h3> | ||
| <button type="button" className="btn btn-ghost btn-sm" onClick={onClose}>{t("common.close")}</button> | ||
| </div> | ||
|
|
||
| <p className="muted small">{aboutKey ? t(aboutKey) : t("codexSet.dialog.unknownLayer")}</p> | ||
|
|
||
| <div className="codex-set-layer-dialog__line"> | ||
| <span className="muted text-label">{t("codexSet.dialog.class")}</span> | ||
| <span>{t(classKey)}</span> | ||
| </div> | ||
|
|
||
| {descriptor.key && ( | ||
| <div className="codex-set-layer-dialog__line"> | ||
| <span className="muted text-label">{t("codexSet.dialog.key")}</span> | ||
| <code className="api-code">{descriptor.key}</code> | ||
| <button | ||
| type="button" | ||
| className="btn btn-ghost btn-sm" | ||
| onClick={() => { void navigator.clipboard?.writeText(descriptor.key ?? ""); }} | ||
| > | ||
| {t("codexSet.dialog.copyKey")} | ||
| </button> | ||
| </div> | ||
| )} | ||
|
|
||
| {descriptor.class === "config-toggle" && toggle && ( | ||
| <div className="codex-set-layer-dialog__line"> | ||
| <span className="muted text-label">{t("codexSet.dialog.fileValue")}</span> | ||
| <span> | ||
| {toggle.userFileValue === null | ||
| ? t("codexSet.dialog.absentDefault", { value: String(toggle.default) }) | ||
| // The default travels with an explicit value too: knowing the file says | ||
| // false is only half the answer without knowing what it would be otherwise. | ||
| : t("codexSet.dialog.setValue", { | ||
| value: String(toggle.userFileValue), | ||
| fallback: String(toggle.default), | ||
| })} | ||
| </span> | ||
| </div> | ||
| )} | ||
|
|
||
| {descriptor.class === "runtime-conditional" && conditionKey && ( | ||
| <p className="muted small">{t(conditionKey)}</p> | ||
| )} | ||
|
|
||
| {/* | ||
| Stated, not implied. A dialog that simply omitted the body would read as | ||
| a loading failure to anyone who expected one. | ||
| */} | ||
| <p className="muted small codex-set-layer-dialog__no-text">{t("codexSet.dialog.noRenderedText")}</p> | ||
| </div> | ||
| </dialog> | ||
| ); | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,98 @@ | ||
| import { useT } from "../../i18n/shared"; | ||
| import { navigateHash } from "../../hash-routing"; | ||
| import type { LayerDescriptorDto, ToggleStateDto } from "../../pages/codex-set-prompt"; | ||
| import { LAYER_LABEL_KEYS } from "./prompt-layer-copy"; | ||
|
|
||
| /** | ||
| * One row of the prompt-layer list. | ||
| * | ||
| * Row kind comes from `descriptor.class`, which is LAYER_INVENTORY, which is the | ||
| * taxonomy in devlog 001 section 4. That is the whole mapping - there is no | ||
| * heuristic and no second table, so a row can never be classified two ways. | ||
| * | ||
| * A layer with no upstream off-switch renders with NO switch element at all: | ||
| * not a disabled checkbox, not a greyed toggle. A disabled control claims the | ||
| * capability exists and is temporarily unavailable, which is false for these | ||
| * layers - Codex has no way to suppress them anywhere. This is ask item 9 at the | ||
| * rendering layer; the API refuses the same ids independently. | ||
| * | ||
| * The wording distinction matters too. `base` and `runtime-conditional` rows have | ||
| * no off-switch anywhere. `feature-gated` rows ARE disableable - through | ||
| * [features], not from this page. Applying the stronger sentence to both would | ||
| * tell a user a setting does not exist when it does. | ||
| */ | ||
| export default function PromptLayerRow({ | ||
| descriptor, | ||
| toggle, | ||
| busy, | ||
| writesRefused, | ||
| onToggle, | ||
| onOpen, | ||
| }: { | ||
| descriptor: LayerDescriptorDto; | ||
| toggle: ToggleStateDto | undefined; | ||
| busy: boolean; | ||
| writesRefused: boolean; | ||
| onToggle: (id: string, enabled: boolean) => void; | ||
| onOpen: (id: string) => void; | ||
| }) { | ||
| const t = useT(); | ||
| const labelKey = LAYER_LABEL_KEYS[descriptor.id]; | ||
| // An id this build has no copy for is shown verbatim rather than blank: a newer | ||
| // runtime listing a layer we do not know about is information, not an error. | ||
| const label = labelKey ? t(labelKey) : descriptor.id; | ||
| const checked = toggle?.defaultedUserValue ?? descriptor.default ?? true; | ||
|
|
||
| return ( | ||
| <li className="codex-set-prompt__row" data-layer-id={descriptor.id} data-layer-class={descriptor.class}> | ||
| <button | ||
| type="button" | ||
| className="link-btn codex-set-prompt__name" | ||
| onClick={() => onOpen(descriptor.id)} | ||
| > | ||
| {label} | ||
| </button> | ||
|
|
||
| {descriptor.key && <code className="codex-set-prompt__key">{descriptor.key}</code>} | ||
|
|
||
| {descriptor.class === "config-toggle" ? ( | ||
| <label className="switch"> | ||
| <input | ||
| type="checkbox" | ||
| role="switch" | ||
| aria-label={label} | ||
| checked={checked} | ||
| disabled={busy || writesRefused} | ||
| onChange={e => { onToggle(descriptor.id, e.target.checked); }} | ||
| /> | ||
| <span className="switch-track" aria-hidden="true" /> | ||
| </label> | ||
| ) : descriptor.class === "feature-gated" ? ( | ||
| // Configurable, just not here. Naming the governing key is the whole point - | ||
| // "always on" would be a lie about a setting the user can actually change - | ||
| // and the link is what turns that from a dead end into a destination. | ||
| <span className="codex-set-prompt__note"> | ||
| {t("codexSet.row.featureGated")}{" "} | ||
| {/* | ||
| A button through `navigateHash`, not a bare fragment href. Nothing in | ||
| the document carries `id="integrations/codex"` - it is a route, so the | ||
| anchor pointed at a target that does not exist and the app router had | ||
| to rescue the click. | ||
| */} | ||
| <button | ||
| type="button" | ||
| className="link-btn" | ||
| onClick={() => navigateHash("integrations/codex")} | ||
| > | ||
| {t("codexSet.row.openFeatures")} | ||
| </button> | ||
| </span> | ||
| ) : ( | ||
| // base and runtime-conditional: no off-switch exists anywhere in Codex. | ||
| <span className="codex-set-prompt__note codex-set-prompt__note--locked"> | ||
| {t("codexSet.row.alwaysOn")} | ||
| </span> | ||
| )} | ||
| </li> | ||
| ); | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,91 @@ | ||
| import type { TKey } from "../../i18n/en"; | ||
| import type { LayerClass } from "../../pages/codex-set-prompt"; | ||
|
|
||
| /** | ||
| * Every layer id in LAYER_INVENTORY, as a finite union. | ||
| * | ||
| * This is what makes the maps below exhaustive. `Record<string, TKey>` does NOT: | ||
| * `string` has no required members, so deleting an entry typechecks cleanly and | ||
| * the non-null assertion at the call site hides the missing lookup until a user | ||
| * sees a blank row. A union has required members, so a gap is a build failure. | ||
| */ | ||
| export type LayerId = | ||
| | "base-instructions" | ||
| | "model-switch" | ||
| | "personality" | ||
| | "context-window-guidance" | ||
| | "realtime" | ||
| | "agents-md" | ||
| | "permissions" | ||
| | "collaboration" | ||
| | "environment" | ||
| | "environments-instructions" | ||
| | "apps" | ||
| | "plugins" | ||
| | "tools" | ||
| | "skills" | ||
| | "multi-agent-mode"; | ||
|
|
||
| /** | ||
| * Layer id -> i18n key, written out rather than built by string concatenation. | ||
| * | ||
| * A template like `("codexSet.layer." + id) as never` typechecks whether or not | ||
| * the key exists, so a missing translation reaches the user as a raw key instead | ||
| * of failing the build. | ||
| */ | ||
| export const LAYER_LABEL_KEYS: Record<LayerId, TKey> = { | ||
| "base-instructions": "codexSet.layer.base-instructions", | ||
| "model-switch": "codexSet.layer.model-switch", | ||
| personality: "codexSet.layer.personality", | ||
| "context-window-guidance": "codexSet.layer.context-window-guidance", | ||
| realtime: "codexSet.layer.realtime", | ||
| "agents-md": "codexSet.layer.agents-md", | ||
| permissions: "codexSet.layer.permissions", | ||
| collaboration: "codexSet.layer.collaboration", | ||
| environment: "codexSet.layer.environment", | ||
| "environments-instructions": "codexSet.layer.environments-instructions", | ||
| apps: "codexSet.layer.apps", | ||
| plugins: "codexSet.layer.plugins", | ||
| tools: "codexSet.layer.tools", | ||
| skills: "codexSet.layer.skills", | ||
| "multi-agent-mode": "codexSet.layer.multi-agent-mode", | ||
| }; | ||
|
|
||
| export const LAYER_ABOUT_KEYS: Record<LayerId, TKey> = { | ||
| "base-instructions": "codexSet.about.base-instructions", | ||
| "model-switch": "codexSet.about.model-switch", | ||
| personality: "codexSet.about.personality", | ||
| "context-window-guidance": "codexSet.about.context-window-guidance", | ||
| realtime: "codexSet.about.realtime", | ||
| "agents-md": "codexSet.about.agents-md", | ||
| permissions: "codexSet.about.permissions", | ||
| collaboration: "codexSet.about.collaboration", | ||
| environment: "codexSet.about.environment", | ||
| "environments-instructions": "codexSet.about.environments-instructions", | ||
| apps: "codexSet.about.apps", | ||
| plugins: "codexSet.about.plugins", | ||
| tools: "codexSet.about.tools", | ||
| skills: "codexSet.about.skills", | ||
| "multi-agent-mode": "codexSet.about.multi-agent-mode", | ||
| }; | ||
|
|
||
| /** | ||
| * Only runtime-conditional rows state a condition, so this map is deliberately | ||
| * PARTIAL - a Partial<Record<LayerId, TKey>> rather than a total one, because | ||
| * requiring an entry for every layer would mean inventing conditions that do not | ||
| * exist. | ||
| */ | ||
| export const LAYER_CONDITION_KEYS: Partial<Record<LayerId, TKey>> = { | ||
| "model-switch": "codexSet.condition.model-switch", | ||
| realtime: "codexSet.condition.realtime", | ||
| "agents-md": "codexSet.condition.agents-md", | ||
| plugins: "codexSet.condition.plugins", | ||
| }; | ||
|
|
||
| export const CLASS_LABEL_KEYS: Record<LayerClass, TKey> = { | ||
| base: "codexSet.class.base", | ||
| "config-toggle": "codexSet.class.config-toggle", | ||
| "feature-gated": "codexSet.class.feature-gated", | ||
| "runtime-conditional": "codexSet.class.runtime-conditional", | ||
| "extension-unknown": "codexSet.class.extension-unknown", | ||
| }; |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This changes the shipped page name and adds the Prompt workflow, but the directly affected documentation still tells users to navigate to Codex Auth—for example,
docs-site/src/content/docs/guides/web-dashboard.mdlines 50 and 104–106—and does not document the new prompt controls; the translated guides remain stale as well. Update the English dashboard guide and its localized counterparts so the documented navigation and capabilities match the UI.AGENTS.md reference: gui/AGENTS.md:L36-L36
Useful? React with 👍 / 👎.