Clarify task-level model overrides in issue properties (#9710)
## Thinking Path > - Paperclip is the control plane where operators configure and inspect AI-agent work. > - An issue can override the assigned agent's primary model for that task. > - The issue-properties UI labeled that per-task setting as `Custom · <model>`, which could be read as a property of the model rather than a replacement of the agent default. > - Operators need the UI to distinguish the agent's primary model from an issue-specific override at both the collapsed summary and selection point. > - This pull request renames the lane presentation to `Override` and adds concise provenance text without changing the stored lane value or adapter configuration behavior. > - The benefit is that operators can immediately understand which model will run and why it differs from the agent default. ## Linked Issues or Issue Description No matching public GitHub issue was found. Related implementation work: #9700 moved Codex ACPX model configuration to startup and is already on `master`; #9355 also addresses ACP session-config rejection behavior. Those PRs concern execution behavior, while this PR is limited to clarifying the issue-level override UI. Bug report details: - **What happened?** The issue-properties Model row displayed `Custom · <model>` for a task-level `assigneeAdapterOverrides.adapterConfig.model`. Operators could misread `Custom` as describing the model itself and could not see that the value replaced the agent's primary model for this issue. - **Expected behavior:** The UI should explicitly identify a task-level model as an override and explain that it replaces the agent's primary model for the issue. - **Steps to reproduce:** Configure an agent with a primary model, set a different model on an issue, and inspect the Model row and model picker in Issue Properties. - **Paperclip version or commit:** Reproduced on the pre-change branch derived from current `master`. - **Deployment mode:** Local development or any deployment using the board UI. - **Installation method:** Built from source. - **Agent adapters involved:** Adapter-agnostic; any adapter exposing model selection. - **Database mode:** Not database-related. - **Access context:** Board operator viewing issue properties. - **Relevant logs or output:** Not applicable; this is a presentation ambiguity. - **Relevant config:** An issue-level `assigneeAdapterOverrides.adapterConfig.model` differing from the assigned agent's primary model. - **Additional context:** The internal lane identifier remains `custom`; only user-facing copy and explanatory text change. - **Privacy checklist:** No private instance links, internal ticket IDs, secrets, usernames, or local paths are included. ## What Changed - Renamed the collapsed issue model label from `Custom · <model>` to `Override · <model>` and added a provenance tooltip. - Renamed the model-picker lane from `Custom` to `Override` and added explanatory subtext at the selection point. - Updated the Issue Properties component test to assert the new label. - Added an isolated Storybook fixture for the task model override state so visual review has a stable target. ## Verification - `pnpm exec vitest run ui/src/components/IssueProperties.test.tsx` — 43 tests passed. - `pnpm --filter @paperclipai/ui typecheck` — passed. - `git diff --check public-gh/master...HEAD` — passed. - `pnpm check:token-gates` — the changed files are clean, but the repo-wide command currently reports nine unrelated `#9627` comment references already present on `master` as color literals. ## Risks - Low risk: this changes display strings and explanatory copy only; override storage, lane identifiers, API contracts, and execution behavior are unchanged. - The global token-gate false positive may also appear in CI until the unrelated `#9627` references on `master` are allowlisted or the scanner ignores issue-number comments. > For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and discuss it in `#dev` before opening the PR. Feature PRs that overlap with planned core work may need to be redirected — check the roadmap first. See `CONTRIBUTING.md`. ## Model Used OpenAI Codex coding agent assisted this PR preparation using medium reasoning, repository-aware shell execution, Git/GitHub tooling, and local test/typecheck execution. The hosted runtime did not expose an exact model ID or context-window size to this session. ## Checklist - [x] I have included a thinking path that traces from project context to this change - [x] I have specified the model used (with version and capability details) - [x] I have checked ROADMAP.md and confirmed this PR does not duplicate planned core work - [x] I have searched GitHub for duplicate or related PRs and linked them above - [x] I have either (a) linked existing issues with `Fixes: #` / `Closes #` / `Refs #` OR (b) described the issue in-PR following the relevant issue template - [x] I have not referenced internal/instance-local Paperclip issues or links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip` URLs) - [x] My branch name describes the change (e.g. `docs/...`, `fix/...`) and contains no internal Paperclip ticket id or instance-derived details - [x] I have run tests locally and they pass - [x] I have added or updated tests where applicable - [x] I have updated relevant documentation to reflect my changes - [x] I have considered and documented any risks above - [x] All Paperclip CI gates are green - [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups - [x] I will address all Greptile and reviewer comments before requesting merge --------- Co-authored-by: Paperclip <noreply@paperclip.ing>
This commit is contained in:
parent
52aea90263
commit
89af58b6dc
|
|
@ -1631,7 +1631,7 @@ describe("IssueProperties", () => {
|
|||
await flush();
|
||||
await flush();
|
||||
|
||||
expect(container.textContent).toContain("Custom · gpt-5.4 · high");
|
||||
expect(container.textContent).toContain("Override · gpt-5.4 · high");
|
||||
expect(container.textContent).toContain("Model lane");
|
||||
|
||||
// Wait for the adapter-models query to resolve so the model options render.
|
||||
|
|
|
|||
|
|
@ -512,9 +512,13 @@ export function IssueProperties({
|
|||
assigneeOverrideThinkingEffort,
|
||||
assigneeOverrideChrome ? "Chrome" : "",
|
||||
].filter(Boolean);
|
||||
const summary = details.length > 0 ? `Override · ${details.join(" · ")}` : "Override · adapter options";
|
||||
return (
|
||||
<span className="min-w-0 truncate text-sm" title={details.length > 0 ? `Custom · ${details.join(" · ")}` : "Custom adapter options"}>
|
||||
Custom{details.length > 0 ? ` · ${details.join(" · ")}` : " adapter options"}
|
||||
<span
|
||||
className="min-w-0 truncate text-sm"
|
||||
title={`Task-level model override — replaces the agent's primary model for this issue.${details.length > 0 ? ` (${details.join(" · ")})` : ""}`}
|
||||
>
|
||||
{summary}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
|
@ -537,7 +541,7 @@ export function IssueProperties({
|
|||
)}
|
||||
onClick={() => setAssigneeOverrideLane(lane)}
|
||||
>
|
||||
{lane === "primary" ? "Primary" : lane === "cheap" ? "Cheap" : "Custom"}
|
||||
{lane === "primary" ? "Primary" : lane === "cheap" ? "Cheap" : "Override"}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
|
@ -551,6 +555,11 @@ export function IssueProperties({
|
|||
: <>· falls back to the primary model if no cheap profile is configured</>}
|
||||
</p>
|
||||
) : null}
|
||||
{assigneeOverrideLane === "custom" ? (
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Task-level model override — replaces the agent's primary model for this issue.
|
||||
</p>
|
||||
) : null}
|
||||
</div>
|
||||
{assigneeOverrideLane === "custom" ? (
|
||||
<>
|
||||
|
|
|
|||
|
|
@ -62,6 +62,19 @@ const primaryIssue: Issue = {
|
|||
documentSummaries: issueDocumentSummaries,
|
||||
currentExecutionWorkspace: storybookExecutionWorkspaces[0]!,
|
||||
};
|
||||
const modelOverrideIssue: Issue = {
|
||||
...primaryIssue,
|
||||
id: "issue-model-override-visual",
|
||||
identifier: "MOD-1",
|
||||
issueNumber: 1,
|
||||
title: "Verify task-level model override provenance",
|
||||
assigneeAdapterOverrides: {
|
||||
adapterConfig: {
|
||||
model: "gpt-5.6",
|
||||
modelReasoningEffort: "high",
|
||||
},
|
||||
},
|
||||
};
|
||||
const childIssues = storybookIssues.filter((issue) => issue.parentId === primaryIssue.id);
|
||||
const longProject: Project = {
|
||||
...storybookProjects[0]!,
|
||||
|
|
@ -311,6 +324,27 @@ function IssuePropertiesLongValuePane({ inline = false }: { inline?: boolean })
|
|||
);
|
||||
}
|
||||
|
||||
function IssuePropertiesModelOverridePane() {
|
||||
return (
|
||||
<StorybookData>
|
||||
<div className="paperclip-story p-6">
|
||||
<div className="mx-auto w-80 border border-border bg-card">
|
||||
<div className="border-b border-border px-4 py-2 text-sm font-medium">Properties</div>
|
||||
<div className="p-4">
|
||||
<IssueProperties
|
||||
issue={modelOverrideIssue}
|
||||
childIssues={[]}
|
||||
onAddSubIssue={() => undefined}
|
||||
onUpdate={() => undefined}
|
||||
inline
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</StorybookData>
|
||||
);
|
||||
}
|
||||
|
||||
function ColumnConfigurationMatrix() {
|
||||
const [columns, setColumns] = useState<InboxIssueColumn[]>(visibleColumns);
|
||||
const visibleColumnSet = useMemo(() => new Set(columns), [columns]);
|
||||
|
|
@ -888,6 +922,11 @@ export const IssuePropertiesLongValuesMobile: Story = {
|
|||
render: () => <IssuePropertiesLongValuePane inline />,
|
||||
};
|
||||
|
||||
export const IssuePropertiesModelOverride: Story = {
|
||||
name: "IssueProperties - task model override",
|
||||
render: () => <IssuePropertiesModelOverridePane />,
|
||||
};
|
||||
|
||||
function ModelProfileLedgerStandalone() {
|
||||
return (
|
||||
<StorybookData>
|
||||
|
|
|
|||
Loading…
Reference in New Issue