Compare commits
2
Commits
323f4caa50
...
91ae7912ab
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
91ae7912ab | ||
|
|
3c4e866c2d |
No files matched your search
@@ -4,6 +4,19 @@ This file is the integrated default-branch snapshot. Feature tasks record progre
|
|||||||
|
|
||||||
## Integrated Through
|
## Integrated Through
|
||||||
|
|
||||||
|
- AI Design confirmation-handoff client source commit
|
||||||
|
`1a52831b26a226ee675c815e8975b4258a10fa18` from task
|
||||||
|
`20260908-design-confirmation-handoff-client-8c3f1a72` is integrated as product
|
||||||
|
commit `3c4e866c2d3847d870f2502f8232ab19e35f1e8e` by task
|
||||||
|
`20260908-design-confirmation-integration-client-8d4b6f20`. The active creation
|
||||||
|
card now remains visible for meaningful canonical content even when the compact
|
||||||
|
concept field is empty. A missing aspect ratio is visibly unselected and must be
|
||||||
|
written through typed Design operations before Quote preparation. An offered Quote
|
||||||
|
retains the explicit confirmation control, which submits only the server-issued
|
||||||
|
Quote ID; chat text and the preparation action still cannot synthesize a Task.
|
||||||
|
Verification passed 64 focused tests, TypeScript, scoped ESLint, all Vite production
|
||||||
|
targets, and exact-range diff checks. No Electron contract, DTO, persistence,
|
||||||
|
package, deployment, or production activation changed.
|
||||||
- AI Design chat-to-Quote client regression source commit
|
- AI Design chat-to-Quote client regression source commit
|
||||||
`6b1e3937d709b74d407379c210a4bf09e8f4b158` from task
|
`6b1e3937d709b74d407379c210a4bf09e8f4b158` from task
|
||||||
`20260907-design-chat-generation-handoff-client-7d3a9c41` is integrated as merge
|
`20260907-design-chat-generation-handoff-client-7d3a9c41` is integrated as merge
|
||||||
|
|||||||
+69
@@ -0,0 +1,69 @@
|
|||||||
|
# Task: Integrate AI Design confirmation handoff client
|
||||||
|
|
||||||
|
## Identity
|
||||||
|
|
||||||
|
- Task ID: 20260908-design-confirmation-integration-client-8d4b6f20
|
||||||
|
- Mode: Integration
|
||||||
|
- Branch: main
|
||||||
|
- Worktree: D:\Datas\OthersProjects\makelore
|
||||||
|
- Base commit: 323f4caa5037daddb4336a723f5c62b8d19a0742
|
||||||
|
- Owner: codex-root
|
||||||
|
- Status: Ready for integration
|
||||||
|
|
||||||
|
## Scope
|
||||||
|
|
||||||
|
- Integrate reviewed source commit `1a52831b26a226ee675c815e8975b4258a10fa18`
|
||||||
|
onto the exact local `main` frontier.
|
||||||
|
- Make an incomplete but meaningful AI Design plan visible, require an explicit
|
||||||
|
aspect-ratio choice when absent, request the immutable Quote, and create a Task
|
||||||
|
only after the user confirms that offered Quote.
|
||||||
|
- Record the integrated state and verification without changing Electron contracts,
|
||||||
|
server DTOs, packaging, deployment, or unrelated worktree files.
|
||||||
|
|
||||||
|
## Intent And Constraints
|
||||||
|
|
||||||
|
- Renderer readiness is only permission to check a production plan, never authority
|
||||||
|
to create a Generation Task.
|
||||||
|
- Missing ratio must remain visibly unselected; the user's choice is written through
|
||||||
|
typed `design.input.apply` operations before requesting a Quote.
|
||||||
|
- Confirmation submits only the server-issued Quote ID.
|
||||||
|
- Preserve the three unrelated untracked task records already present in the root
|
||||||
|
worktree; do not stage, rewrite, stash, reset, or clean them.
|
||||||
|
|
||||||
|
## Outcome
|
||||||
|
|
||||||
|
- Cherry-picked source commit `1a52831b26a226ee675c815e8975b4258a10fa18`
|
||||||
|
onto the exact source-parent `main` frontier as product commit
|
||||||
|
`3c4e866c2d3847d870f2502f8232ab19e35f1e8e`.
|
||||||
|
- Confirmed that every source product/test path is byte-equivalent. The only
|
||||||
|
source-to-product difference is intentional omission of the source task's
|
||||||
|
read-only record, which remains on its source branch.
|
||||||
|
- Promoted the incomplete-plan visibility, explicit aspect-ratio choice, and
|
||||||
|
Quote-ID confirmation handoff into canonical integrated state.
|
||||||
|
- Preserved the three pre-existing untracked task records without staging or editing
|
||||||
|
them.
|
||||||
|
|
||||||
|
## Verification
|
||||||
|
|
||||||
|
- `pnpm exec vitest run tests/unit/image-canvas-page.test.tsx
|
||||||
|
tests/unit/youth-creation-card.test.tsx tests/unit/image-workspace-store.test.ts`
|
||||||
|
— 3 files, 64 tests passed.
|
||||||
|
- `pnpm typecheck` — passed.
|
||||||
|
- Scoped ESLint for both changed production modules and the changed test — passed.
|
||||||
|
- `pnpm build:vite` — passed; only existing Browserslist, mixed-import, and chunk-size
|
||||||
|
warnings were emitted.
|
||||||
|
- `git diff --check 323f4caa5037daddb4336a723f5c62b8d19a0742..3c4e866c2d3847d870f2502f8232ab19e35f1e8e`
|
||||||
|
— passed.
|
||||||
|
- `check_project_docs.py --target .` — passed.
|
||||||
|
- Task-aware `check_doc_drift.py` — passed while retaining the three adopted,
|
||||||
|
unchanged foreign task records.
|
||||||
|
|
||||||
|
## Follow-ups
|
||||||
|
|
||||||
|
- Run an authenticated packaged-client smoke after the paired server/client rollout:
|
||||||
|
meaningful chat -> missing-ratio selection -> offered Quote -> Quote-ID confirmation
|
||||||
|
-> authoritative Task projection.
|
||||||
|
|
||||||
|
## Promotion Candidates
|
||||||
|
|
||||||
|
- None recorded.
|
||||||
@@ -70,7 +70,12 @@ function canonicalCreatorPrompt(workspace: DesignWorkspace): string {
|
|||||||
const values = workspace.form.specification.values;
|
const values = workspace.form.specification.values;
|
||||||
return values.content.concept?.trim()
|
return values.content.concept?.trim()
|
||||||
|| values.content.narrative?.trim()
|
|| values.content.narrative?.trim()
|
||||||
|| '';
|
|| [
|
||||||
|
...values.content.subjects.map((subject) => subject.description.trim()),
|
||||||
|
values.content.action?.trim(),
|
||||||
|
values.content.environment?.trim(),
|
||||||
|
values.content.emotional_tone?.trim(),
|
||||||
|
].filter((value): value is string => Boolean(value)).join(',');
|
||||||
}
|
}
|
||||||
|
|
||||||
function Stepper({
|
function Stepper({
|
||||||
@@ -141,7 +146,7 @@ export function YouthCreationCard({
|
|||||||
const [replaceIndex, setReplaceIndex] = useState<number | null>(null);
|
const [replaceIndex, setReplaceIndex] = useState<number | null>(null);
|
||||||
const [uploading, setUploading] = useState(false);
|
const [uploading, setUploading] = useState(false);
|
||||||
const [medium, setMedium] = useState(values.intent.media ?? 'image');
|
const [medium, setMedium] = useState(values.intent.media ?? 'image');
|
||||||
const [aspectRatio, setAspectRatio] = useState(values.output.aspect_ratio ?? '1:1');
|
const [aspectRatio, setAspectRatio] = useState(values.output.aspect_ratio ?? '');
|
||||||
const [duration, setDuration] = useState(values.video.total_duration_seconds ?? 6);
|
const [duration, setDuration] = useState(values.video.total_duration_seconds ?? 6);
|
||||||
const [variantCount, setVariantCount] = useState(values.output.variant_count ?? 1);
|
const [variantCount, setVariantCount] = useState(values.output.variant_count ?? 1);
|
||||||
const promptRef = useRef<HTMLTextAreaElement>(null);
|
const promptRef = useRef<HTMLTextAreaElement>(null);
|
||||||
@@ -155,7 +160,7 @@ export function YouthCreationCard({
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setMedium(values.intent.media ?? 'image');
|
setMedium(values.intent.media ?? 'image');
|
||||||
setAspectRatio(values.output.aspect_ratio ?? '1:1');
|
setAspectRatio(values.output.aspect_ratio ?? '');
|
||||||
setDuration(values.video.total_duration_seconds ?? 6);
|
setDuration(values.video.total_duration_seconds ?? 6);
|
||||||
setVariantCount(values.output.variant_count ?? 1);
|
setVariantCount(values.output.variant_count ?? 1);
|
||||||
}, [
|
}, [
|
||||||
@@ -602,6 +607,7 @@ export function YouthCreationCard({
|
|||||||
], () => setAspectRatio(next));
|
], () => setAspectRatio(next));
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
<option value="" disabled>请选择</option>
|
||||||
{ASPECT_RATIOS.map((item) => <option key={item.value} value={item.value}>{item.label}</option>)}
|
{ASPECT_RATIOS.map((item) => <option key={item.value} value={item.value}>{item.label}</option>)}
|
||||||
</Select>
|
</Select>
|
||||||
</label>
|
</label>
|
||||||
|
|||||||
@@ -1,13 +1,22 @@
|
|||||||
import type { DesignWorkspace } from '../../../shared/image-workspace';
|
import type { DesignWorkspace } from '../../../shared/image-workspace';
|
||||||
|
|
||||||
function hasCreatorPrompt(workspace: DesignWorkspace): boolean {
|
function hasCreatorMeaning(workspace: DesignWorkspace): boolean {
|
||||||
const values = workspace.form.specification.values;
|
const values = workspace.form.specification.values;
|
||||||
return Boolean(values.content.concept?.trim() || values.content.narrative?.trim());
|
return Boolean(
|
||||||
|
values.content.concept?.trim()
|
||||||
|
|| values.content.narrative?.trim()
|
||||||
|
|| values.content.action?.trim()
|
||||||
|
|| values.content.environment?.trim()
|
||||||
|
|| values.content.emotional_tone?.trim()
|
||||||
|
|| values.content.subjects.some((subject) => subject.description.trim())
|
||||||
|
|| values.content.exact_content.some((item) => item.text.trim())
|
||||||
|
|| values.references.length > 0,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function hasActiveCreationPlan(workspace: DesignWorkspace): boolean {
|
export function hasActiveCreationPlan(workspace: DesignWorkspace): boolean {
|
||||||
if (workspace.form.activeQuotes.some((quote) => quote.status === 'offered')) return true;
|
if (workspace.form.activeQuotes.some((quote) => quote.status === 'offered')) return true;
|
||||||
if (!hasCreatorPrompt(workspace) && workspace.form.specification.values.references.length === 0) return false;
|
if (!hasCreatorMeaning(workspace)) return false;
|
||||||
const latestTaskRevision = workspace.tasks.reduce(
|
const latestTaskRevision = workspace.tasks.reduce(
|
||||||
(latest, task) => Math.max(latest, task.specificationRevision),
|
(latest, task) => Math.max(latest, task.specificationRevision),
|
||||||
-1,
|
-1,
|
||||||
|
|||||||
@@ -192,6 +192,40 @@ describe('AI Design Canvas page', () => {
|
|||||||
expect(screen.queryByTestId('image-workspace-works-rail')).not.toBeInTheDocument();
|
expect(screen.queryByTestId('image-workspace-works-rail')).not.toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('shows an incomplete meaningful plan and does not invent a selected aspect ratio', async () => {
|
||||||
|
const form = designFormFixture();
|
||||||
|
form.specification.values.content.concept = null;
|
||||||
|
form.specification.values.content.action = '一只狗和一只猫在草地上互相扑咬玩闹';
|
||||||
|
form.specification.values.output.aspect_ratio = null;
|
||||||
|
form.specification.values.output.orientation = 'portrait';
|
||||||
|
const { actions } = prepareWorkspace({ form });
|
||||||
|
useImageWorkspaceStore.setState({
|
||||||
|
quoteBlockers: [{
|
||||||
|
code: 'aspect_ratio_required',
|
||||||
|
message: 'Choose an aspect ratio before requesting a production quote.',
|
||||||
|
severity: 'blocker',
|
||||||
|
path: 'output.aspect_ratio',
|
||||||
|
}],
|
||||||
|
});
|
||||||
|
|
||||||
|
render(<ImageCanvas />);
|
||||||
|
|
||||||
|
expect(screen.getByRole('heading', { name: '制作方案' })).toBeInTheDocument();
|
||||||
|
expect(screen.getByText('先选择作品形状。')).toBeInTheDocument();
|
||||||
|
expect(screen.getByRole('combobox', { name: '画幅' })).toHaveValue('');
|
||||||
|
expect(screen.getByRole('button', { name: '准备制作方案' })).toBeDisabled();
|
||||||
|
|
||||||
|
fireEvent.change(screen.getByRole('combobox', { name: '画幅' }), {
|
||||||
|
target: { value: '9:16' },
|
||||||
|
});
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(actions.applyFieldOperations).toHaveBeenCalledWith([
|
||||||
|
{ kind: 'set', path: 'output.aspect_ratio', value: '9:16' },
|
||||||
|
{ kind: 'set', path: 'output.orientation', value: 'portrait' },
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('opens project navigation as a left sheet on compact layouts', () => {
|
it('opens project navigation as a left sheet on compact layouts', () => {
|
||||||
prepareWorkspace();
|
prepareWorkspace();
|
||||||
render(<ImageCanvas />);
|
render(<ImageCanvas />);
|
||||||
|
|||||||
Reference in new issue
Block a user