8000 feat(ui): name new pipelines for you; Enter creates pipelines and routines and moves from a note's title to its body by adeelahmad · Pull Request #336 · adeelahmad/lense · GitHub
[go: up one dir, main page]

Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
96 changes: 44 additions & 52 deletions ci/clicks/baseline.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"surveyed": "2026-10-10T22:25:49.567Z",
"surveyed": "2026-10-10T22:47:49.235Z",
"commit": "",
"viewport": "1400x900",
"journeys": [
Expand Down Expand Up @@ -45,7 +45,7 @@
}
],
"id": "install",
"ms": 2081,
"ms": 2000,
"ok": true
},
{
Expand Down Expand Up @@ -82,7 +82,7 @@
}
],
"id": "first-admin",
"ms": 475,
"ms": 423,
"ok": true
},
{
Expand Down Expand Up @@ -127,7 +127,7 @@
}
],
"id": "setup-wizard",
"ms": 659,
"ms": 572,
"ok": true
},
{
Expand Down Expand Up @@ -156,7 +156,7 @@
}
],
"id": "first-question",
"ms": 2225,
"ms": 2207,
"ok": true
},
{
Expand Down Expand Up @@ -201,7 +201,7 @@
}
],
"id": "import-paste",
"ms": 4139,
"ms": 4107,
"ok": true
},
{
Expand Down Expand Up @@ -230,7 +230,7 @@
}
],
"id": "library-open",
"ms": 2621,
"ms": 2525,
"ok": true
},
{
Expand Down Expand Up @@ -267,7 +267,7 @@
}
],
"id": "search-word",
"ms": 3173,
"ms": 3176,
"ok": true
},
{
Expand Down Expand Up @@ -304,7 +304,7 @@
}
],
"id": "jump-to",
"ms": 2385,
"ms": 2251,
"ok": true
},
{
Expand Down Expand Up @@ -341,15 +341,15 @@
}
],
"id": "chat-ask",
"ms": 3552,
"ms": 3390,
"ok": true
},
{
"module": "Notes",
"feature": "Write a note",
"note": "",
"clicks": 3,
"keys": 35,
"clicks": 2,
"keys": 36,
"prompts": 0,
"steps": [
{
Expand All @@ -376,17 +376,25 @@
"keys": 8,
"prompts": 0
},
{
"page": "/notes/:id",
"kind": "key",
"what": "Enter to go to the first line",
"clicks": 0,
"keys": 1,
"prompts": 0
},
{
"page": "/notes/:id",
"kind": "type",
"what": "first line",
"clicks": 1,
"clicks": 0,
"keys": 27,
"prompts": 0
}
],
"id": "note-create",
"ms": 3366,
"ms": 3254,
"ok": true
},
{
Expand Down Expand Up @@ -431,15 +439,15 @@
}
],
"id": "topic-add",
"ms": 3487,
"ms": 3368,
"ok": true
},
{
"module": "Pipelines",
"feature": "Make a pipeline from the standard steps",
"note": "",
"clicks": 3,
"keys": 14,
"clicks": 2,
"keys": 1,
"prompts": 0,
"steps": [
{
Expand All @@ -460,31 +468,23 @@
},
{
"page": "/pipelines/new",
"kind": "type",
"what": "name",
"kind": "key",
"what": "Enter to create",
"clicks": 0,
"keys": 14,
"prompts": 0
},
{
"page": "/pipelines/new",
"kind": "click",
"what": "Create pipeline",
"clicks": 1,
"keys": 0,
"keys": 1,
"prompts": 0
}
],
"id": "pipeline-create",
"ms": 2694,
"ms": 2548,
"ok": true
},
{
"module": "Routines",
"feature": "Make a nightly routine (sync sources, the default action)",
"note": "",
"clicks": 3,
"keys": 12,
"clicks": 2,
"keys": 1,
"prompts": 0,
"steps": [
{
Expand All @@ -505,23 +505,15 @@
},
{
"page": "/routines/new",
"kind": "type",
"what": "name",
"kind": "key",
"what": "Enter to create",
"clicks": 0,
"keys": 12,
"prompts": 0
},
{
"page": "/routines/new",
"kind": "click",
"what": "Create routine",
"clicks": 1,
"keys": 0,
"keys": 1,
"prompts": 0
}
],
"id": "routine-create",
"ms": 2647,
"ms": 3298,
"ok": true
},
{
Expand Down Expand Up @@ -558,7 +550,7 @@
}
],
"id": "extension-add",
"ms": 2554,
"ms": 2533,
"ok": true
},
{
Expand Down Expand Up @@ -595,7 +587,7 @@
}
],
"id": "sensor-webhook",
"ms": 2684,
"ms": 2822,
"ok": true
},
{
Expand Down Expand Up @@ -640,7 +632,7 @@
}
],
"id": "settings-change",
"ms": 3491,
"ms": 3430,
"ok": true
},
{
Expand Down Expand Up @@ -701,7 +693,7 @@
}
],
"id": "person-add",
"ms": 3863,
"ms": 3059,
"ok": true
},
{
Expand Down Expand Up @@ -746,7 +738,7 @@
}
],
"id": "api-token",
"ms": 3140,
"ms": 3137,
"ok": true
},
{
Expand Down Expand Up @@ -775,7 +767,7 @@
}
],
"id": "namespace-switch",
"ms": 2280,
"ms": 2256,
"ok": true
}
],
Expand Down Expand Up @@ -892,7 +884,7 @@
"page": "/routines/history",
"label": "Routines › History",
"reach": 2,
"controls": 3
"controls": 6
},
{
"page": "/extensions",
Expand Down Expand Up @@ -1048,7 +1040,7 @@
"page": "/settings/telemetry",
"label": "Settings › Telemetry",
"reach": 2,
"controls": 12
"controls": 13
},
{
"page": "/settings/fedora",
Expand Down Expand Up @@ -1126,7 +1118,7 @@
"page": "/admin/health",
"label": "Admin › System health",
"reach": 2,
"controls": 10
"controls": 11
}
]
}
15 changes: 9 additions & 6 deletions ci/clicks/journeys.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -190,8 +190,9 @@ export const JOURNEYS = [
await page.waitForURL(/\/notes\/\d+/);
const title = page.getByRole("textbox", { name: "Title" });
await meter.replace(title, "Pi setup", "title");
const body = page.locator("main affine-page-root [contenteditable=true]").first();
const editor = (await body.count()) ? body : page.locator("main .inline-editor").last();
const editor = page.locator("main affine-page-root .inline-editor").first();
await editor.waitFor(); // the page has loaded its editor
await meter.press("Enter", "Enter to go to the first line");
await meter.type(editor, "Use cloud models on the Pi.", "first line");
await page.getByText("Saved").first().waitFor();
},
Expand All @@ -217,8 +218,9 @@ export const JOURNEYS = [
async run({ page, meter }) {
await meter.click(nav(page, "Pipelines"), "Pipelines");
await meter.click(page.getByRole("main").getByRole("link", { name: "New pipeline" }), "New pipeline");
await meter.type(page.getByRole("textbox", { name: "Pipeline name" }), "Quick pipeline", "name");
await meter.click(page.getByRole("button", { name: "Create pipeline" }), "Create pipeline");
await page.getByRole("textbox", { name: "Pipeline name" }).waitFor(); // named for you
await page.waitForFunction(() => document.activeElement?.tagName === "INPUT");
await meter.press("Enter", "Enter to create");
await page.waitForURL(/\/pipelines\/\d+/);
},
},
Expand All @@ -230,8 +232,9 @@ export const JOURNEYS = [
async run({ page, meter }) {
await meter.click(nav(page, "Routines"), "Routines");
await meter.click(page.getByRole("main").getByRole("link", { name: "New routine" }), "New routine");
await meter.type(page.getByRole("textbox", { name: "Name" }), "Nightly sync", "name");
await meter.click(page.getByRole("button", { name: "Create routine" }), "Create routine");
await page.getByRole("textbox", { name: "Name" }).waitFor(); // named for you
await page.waitForFunction(() => document.activeElement?.tagName === "INPUT");
await meter.press("Enter", "Enter to create");
await page.waitForURL(/\/routines\/\d+/);
},
},
Expand Down
12 changes: 10 additions & 2 deletions ci/clicks/meter.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,14 @@ export function pageKey(url) {
return (port ? `${port}:` : "") + (path || "/");
}

/** Typing goes into `el` without a click: it has the focus, or (rich text) the caret is in it. */
function hasFocus(el) {
const active = document.activeElement;
if (el === active || el.contains(active)) return true;
const caret = getSelection()?.anchorNode;
return Boolean(el.isContentEditable && active?.contains(el) && caret && el.contains(caret));
}

export class Meter {
constructor(page) {
this.page = page;
Expand Down Expand Up @@ -80,7 +88,7 @@ export class Meter {
/** Types `text` into a field, adding the click that focuses it when it isn't focused already. */
async type(locator, text, what) {
await locator.waitFor();
const focused = await locator.evaluate((el) => el === document.activeElement || el.contains(document.activeElement));
const focused = await locator.evaluate(hasFocus);
this.record("type", what, focused ? 0 : 1, text.length);
if (!focused) await locator.click();
await locator.pressSequentially(text);
Expand All @@ -89,7 +97,7 @@ export class Meter {
/** Pastes `text` into a field: the focusing click when needed, and the paste shortcut. */
async paste(locator, text, what) {
await locator.waitFor();
const focused = await locator.evaluate((el) => el === document.activeElement || el.contains(document.activeElement));
const focused = await locator.evaluate(hasFocus);
this.record("paste", what, focused ? 0 : 1, 1);
await locator.fill(text);
}
Expand Down
11 changes: 11 additions & 0 deletions nextjs-frontend/components/notes/note-page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,17 @@ export function NotePage({ id, about }: { id?: number; about?: string }) {
setTitle(e.target.value);
change({ title: e.target.value });
}}
=> {
// Enter goes on to the page's first line, as in a document
if (e.key !== "Enter" || e.nativeEvent.isComposing) return;
// (the page root is what takes the focus; the caret goes in its first paragraph, not the hidden title line)
const root = document.querySelector<HTMLElement>(".lens-block-editor affine-page-root");
const line = root?.querySelector(".inline-editor");
if (!root || !line) return;
e.preventDefault();
root.focus();
window.getSelection()?.collapse(line, 0);
}}
placeholder="Untitled"
className="w-full bg-transparent text-[30px] font-bold leading-tight tracking-[-.015em] text-fg outline-none placeholder:text-fg-muted"
/>
Expand Down
Loading
Loading
0