-
-
- {t("popup.brandName")}
-
-
-
-
-
-
-
- {t(STATE_LABEL_KEYS[statusState])}
-
-
-
-
- {t(STATE_BADGE_KEYS[statusState])}
-
-
-
-
- {isSkewed && (
-
- {t("popup.versionSkewWarning", {
- extensionProtocol: PROTOCOL_VERSION,
- daemonProtocol,
- })}
-
- )}
-
-
- {snapshot.lastError && (
-
- {snapshot.lastError}
-
- )}
-
-
-
- ) => setLabelDraft(event.target.value)}
- onBlur={onLabelBlur}
- placeholder={t("popup.labelPlaceholder")}
- className="mt-0 h-8 text-sm"
- data-slot="popup-label-input"
- />
-
-
-
-
- {extensionVersion}
- /
- {daemonVersion}
-
-
- {t("popup.instanceTitle")}
-
- {instanceId}
-
+ {view !== "main" ? (
-
-
+ ) : (
+
+ )}
+ {headerTitle}
+ {view === "main" && (
+
+ )}
+
+
+ {view === "main" && (
+ <>
+
+
+
+
+
+ {t(STATE_LABEL_KEYS[statusState])}
+
+
+
+
+ {t(STATE_BADGE_KEYS[statusState])}
+
+
+
+
+ {isSkewed && (
+
+ {t("popup.versionSkewWarning", {
+ extensionProtocol: PROTOCOL_VERSION,
+ daemonProtocol,
+ })}
+
+ )}
+
+
+ {snapshot.lastError && (
+
+ {snapshot.lastError}
+
+ )}
+
+
+
+ {extensionVersion}
+ /
+ {daemonVersion}
+
+
+ {t("popup.instanceTitle")}
+
+ {instanceId}
+
+
+
+
+ >
+ )}
+
+ {view === "features" && (
+
+ {POPUP_FEATURES.map((feature) => {
+ const Icon = feature.icon;
+ return (
+
+ );
+ })}
+
+ )}
+
+ {view === "record" && (
+
+
+ {t("popup.record.sectionHint")}
+
+
+
+ ) => setPurposeDraft(event.target.value)}
+ placeholder={t("popup.record.topicPlaceholder")}
+ className="mt-0 h-8 text-sm"
+ data-slot="popup-record-purpose-input"
+ />
+
+
+
+ ) => setStartUrlDraft(event.target.value)}
+ placeholder={t("popup.record.startUrlPlaceholder")}
+ className="mt-0 h-8 text-sm"
+ data-slot="popup-record-start-url-input"
+ />
+
+
+
+ {!recordReady && (
+
+ {t("popup.record.hintDisconnected")}
+
+ )}
+
+
+
+ )}
);
}
diff --git a/apps/extension/src/entrypoints/popup/features.ts b/apps/extension/src/entrypoints/popup/features.ts
new file mode 100644
index 0000000..0a82dbf
--- /dev/null
+++ b/apps/extension/src/entrypoints/popup/features.ts
@@ -0,0 +1,22 @@
+import { RiRecordCircleLine } from "@remixicon/react";
+import type { RemixiconComponentType } from "@remixicon/react";
+
+export type PopupFeatureId = "record";
+
+export type PopupView = "main" | "features" | PopupFeatureId;
+
+export type PopupFeature = {
+ id: PopupFeatureId;
+ icon: RemixiconComponentType;
+ titleKey: "popup.record.sectionTitle";
+ descKey: "popup.record.cardDesc";
+};
+
+export const POPUP_FEATURES: PopupFeature[] = [
+ {
+ id: "record",
+ icon: RiRecordCircleLine,
+ titleKey: "popup.record.sectionTitle",
+ descKey: "popup.record.cardDesc",
+ },
+];
diff --git a/apps/extension/src/entrypoints/popup/style.css b/apps/extension/src/entrypoints/popup/style.css
index 1d1365a..20ed097 100644
--- a/apps/extension/src/entrypoints/popup/style.css
+++ b/apps/extension/src/entrypoints/popup/style.css
@@ -1,3 +1,13 @@
@import "@browser-skill/ui/styles";
@source "../../";
@source "../../../../../packages/ui/src";
+
+/* The shared base sets html/body/#root to height:100%, which is meant for
+ full-page surfaces. In a popup that pins the window to the tallest content
+ it ever showed, so collapsing the record section leaves blank space. Size
+ to content instead so Chrome can grow and shrink the popup to fit. */
+html,
+body,
+#root {
+ height: auto;
+}
diff --git a/apps/extension/src/lib/__tests__/describe-target.test.ts b/apps/extension/src/lib/__tests__/describe-target.test.ts
index d192803..b265b16 100644
--- a/apps/extension/src/lib/__tests__/describe-target.test.ts
+++ b/apps/extension/src/lib/__tests__/describe-target.test.ts
@@ -1,9 +1,5 @@
import { beforeEach, describe, expect, it } from "vitest";
-import {
- describeEventTarget,
- describeTarget,
- isMeaningfulClickTarget,
-} from "../describe-target";
+import { describeEventTarget, describeTarget, isMeaningfulClickTarget } from "../describe-target";
describe("describeTarget", () => {
beforeEach(() => {
diff --git a/apps/extension/src/lib/describe-target.ts b/apps/extension/src/lib/describe-target.ts
index f447a72..7d3898a 100644
--- a/apps/extension/src/lib/describe-target.ts
+++ b/apps/extension/src/lib/describe-target.ts
@@ -140,7 +140,10 @@ export function accessibleName(el: Element): string | undefined {
if (el instanceof HTMLInputElement || el instanceof HTMLButtonElement) {
const value = normalizeWhitespace(el.value);
- if (value && (el instanceof HTMLButtonElement || el.type === "submit" || el.type === "button")) {
+ if (
+ value &&
+ (el instanceof HTMLButtonElement || el.type === "submit" || el.type === "button")
+ ) {
return truncate(value);
}
}
diff --git a/apps/extension/src/tools/__tests__/record-observation.test.ts b/apps/extension/src/tools/__tests__/record-observation.test.ts
index b63ad9d..1330842 100644
--- a/apps/extension/src/tools/__tests__/record-observation.test.ts
+++ b/apps/extension/src/tools/__tests__/record-observation.test.ts
@@ -2,11 +2,18 @@ import { afterEach, describe, expect, it, vi } from "vitest";
import {
ensureBrowserObservationListeners,
isBrowserObservationAttachedForTests,
+ RECORD_DEFAULT_START_URL,
releaseBrowserObservationListenersIfIdle,
resetBrowserObservationForTests,
setBrowserObservationAttachForTests,
} from "../record";
+describe("record defaults", () => {
+ it("uses Baidu as the default injectable start URL", () => {
+ expect(RECORD_DEFAULT_START_URL).toBe("https://www.baidu.com/");
+ });
+});
+
describe("browser observation lifecycle", () => {
afterEach(() => {
resetBrowserObservationForTests();
diff --git a/apps/extension/src/tools/record.ts b/apps/extension/src/tools/record.ts
index 76defdd..1f9fae3 100644
--- a/apps/extension/src/tools/record.ts
+++ b/apps/extension/src/tools/record.ts
@@ -77,6 +77,9 @@ function sleep(ms: number): Promise