mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-31 14:16:39 +01:00
## Thinking Path > - Paperclip lets people manage AI agents and use their saved work. > - Product E2E journeys check these user paths in classic and Cloud environments. > - The shared Skill Studio helper edits a skill created by an agent. > - Studio can mount the editor before its saved file finishes loading. > - Typing into that empty draft can replace or lose content when the file arrives. > - This change waits for the existing saved body before it starts the edit. > - Browser regressions check delayed loading and refusal to edit an unloaded file. ## Linked Issues or Issue Description Refs #18222 for the shared Product E2E journeys. No duplicate hydration fix was found. **What happened?** The shared Studio helper started typing as soon as the editor control was visible. A delayed file query could replace that draft. The helper could then lose the original content or fail to save its complete marker. **Expected behavior** The helper must load the original file before editing it. It must preserve that content, save the new marker, reload, and read the result from the task card. **Steps to reproduce** Run the controlled Studio browser fixture with a 1.5-second file load. The old helper saves the marker without the original `Verify checks.` body. With no file load, it still sends input events to the empty editor. ## What Changed - Wait for the original saved body before typing in the shared Studio helper. - Add browser checks for immediate load, delayed load, and an unavailable file. - Include these checks in the credential-free browser support suite. - Document the saved-file readiness requirement. ## Verification - Against the old helper, both new failure cases fail: the delayed case loses the original body, and the unloaded case sends 13 input events. - With the change, all three browser checks pass. They verify the saved original body and marker, reload, task-card readback, and zero input or save calls for an unloaded file. - `node node_modules/typescript/bin/tsc -p tests/runner-e2e/tsconfig.json --noEmit` passes. - `git diff --check` passes. Full repository CI passes in [41058312896](https://github.com/paperclipai/paperclip/actions/runs/41058312896), including typecheck, tests, build and all eight E2E shards. Greptile reviewed this exact head at 5/5 with no open threads. No live acceptance result is claimed by these controlled fixtures. ## Risks This adds a readiness assertion to an existing test helper. Both callers already use the same skill fixture and verify `Verify checks.` in the task card. An unavailable file now fails before an edit can occur. The change does not alter application behavior, model instructions, grading, or deadlines. ## Model Used OpenAI Codex, GPT-6 family, with reasoning and code execution. The host does not expose the exact underlying model version or context-window size. ## 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 described the issue in-PR following the bug template - [x] I have not referenced internal/instance-local Paperclip issues or links - [x] My branch name describes the change and contains no internal ticket id - [x] I have run tests locally and they pass - [x] I have added tests where applicable - [x] I have updated relevant documentation - [x] I have considered and documented risks - [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 reviewer comments before merge Co-authored-by: Paperclip <noreply@paperclip.ing>
39 lines
2.1 KiB
TypeScript
39 lines
2.1 KiB
TypeScript
import { expect, type Page } from "@playwright/test";
|
|
|
|
/** Shared browser story for classic and Cloud: activity card -> Studio -> saved task readback. */
|
|
export async function editCreatedSkillThroughStudio(input: {
|
|
page: Page; skillId: string; skillName: string; marker: string; deadlineAt: number;
|
|
read(): Promise<unknown>;
|
|
}) {
|
|
const { page, skillName } = input;
|
|
const card = page.getByRole("article", { name: `Skill created: ${skillName}`, exact: true });
|
|
await expect(card).toHaveCount(1);
|
|
await expect(card).toBeVisible();
|
|
await card.getByRole("button").click();
|
|
await expect(page.getByRole("heading", { name: skillName, exact: true })).toBeVisible();
|
|
await expect(page.getByText("Verify checks.", { exact: true })).toBeVisible();
|
|
await page.getByRole("button", { name: "Open in Skill Studio", exact: true }).click();
|
|
await expect(page).toHaveURL(new RegExp(`/skills/studio/${input.skillId}$`));
|
|
await expect(page.getByRole("combobox").filter({ hasText: skillName })).toBeVisible();
|
|
const editor = page.getByRole("textbox", { name: "editable markdown", exact: true });
|
|
// Studio mounts the editor before its file query completes. Wait for the
|
|
// saved body, otherwise hydration can replace edits made to the empty draft.
|
|
await expect(editor).toContainText("Verify checks.");
|
|
await editor.click();
|
|
await editor.press("ControlOrMeta+End");
|
|
await editor.press("Enter");
|
|
await editor.press("Enter");
|
|
await editor.pressSequentially(input.marker);
|
|
await page.getByRole("button", { name: /^Save$/ }).click();
|
|
await expect.poll(async () => JSON.stringify(await input.read()).includes(input.marker),
|
|
{ timeout: Math.max(1, Math.min(30_000, input.deadlineAt - Date.now())) }).toBe(true);
|
|
await page.reload();
|
|
await expect(editor).toContainText(input.marker);
|
|
await page.goBack();
|
|
await expect(page).toHaveURL(/\/issues\//);
|
|
await expect(card).toBeVisible();
|
|
await card.getByRole("button").click();
|
|
await expect(page.getByRole("heading", { name: skillName, exact: true })).toBeVisible();
|
|
await expect(page.getByText(input.marker, { exact: true })).toBeVisible();
|
|
}
|