Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
104 commits
Select commit Hold shift + click to select a range
1c1bbc0
feat: prepare native Reader contract-scoped reads
callumalpass Oct 10, 2026
8107c1d
fix: fence Reader delivery after provider recheck
callumalpass Oct 10, 2026
b9eaf73
chore: pin qualified corrected native SDK archive
callumalpass Oct 10, 2026
000b092
chore: pin qualified shared read-helper SDK successor
callumalpass Oct 10, 2026
6a501e4
chore: pin qualified explicit-environment SDK successor
callumalpass Oct 10, 2026
da496eb
add public SDK sign-in UI with explicit collection boundary (#64)
callumalpass Oct 10, 2026
4d73bca
reuse shared controls for native sign-in actions (#65)
callumalpass Oct 10, 2026
19dc015
Close the owned Connect popup after consent completes (#66)
callumalpass Oct 10, 2026
93ae8de
Pin release-qualified SDK819 archive (#67)
callumalpass Oct 10, 2026
e34006b
Pin Reader to original 563dc868 SDK dev archive
callumalpass Oct 11, 2026
cd5fb28
Repin Reader to the ab00e5eb SDK dev archive
callumalpass Oct 11, 2026
f68dec6
Repin Reader to the 7193fd0d SDK dev archive
callumalpass Oct 11, 2026
fcfd41e
Add a native SDK collection adapter behind @mdbase-reader/connect/native
callumalpass Oct 11, 2026
2d872b1
Wire Reader's LAB builds to the live SDK entry
callumalpass Oct 11, 2026
0005c04
Repin Reader to the d53d7d76 SDK dev archive
callumalpass Oct 11, 2026
dc8d007
Repin Reader to the df091839 SDK dev archive
callumalpass Oct 11, 2026
8570658
Use the SDK's contract-scoped records and file media types
callumalpass Oct 11, 2026
430a54c
Repin Reader to the 38001079 SDK dev archive and enable the contract …
callumalpass Oct 11, 2026
2be37ec
reader: pin original native n1 SDK for LAB preparation
callumalpass Oct 11, 2026
63af704
reader: use current native SDK collection for library imports
callumalpass Oct 11, 2026
cb2a0aa
reader: pin original DEV LAB SDK 4b955381
callumalpass Oct 11, 2026
4c8b806
reader: pin original DEV LAB SDK 6a576a5f
callumalpass Oct 11, 2026
4307ee9
Reader: SDK describeError screens for blocked, readiness and incompat…
callumalpass Oct 11, 2026
935a6bd
reader: review setup changes before applying them
callumalpass Oct 11, 2026
020202b
reader: setup review reports unconfirmed outcomes, skips empty plans,…
callumalpass Oct 11, 2026
21410f2
reader: consolidate public SDK launch entry and exact 41c intake
callumalpass Oct 11, 2026
75740e8
extension: run the new SDK in extension pages
callumalpass Oct 11, 2026
ac8dfaf
extension: one offscreen SDK host shared by every context
callumalpass Oct 11, 2026
a126df8
extension: fence the SDK RPC to the caller and the host generation
callumalpass Oct 11, 2026
ead6723
Document trusted extension SDK transport and lifecycle
callumalpass Oct 11, 2026
978b61d
extension: refused or expired sign-in offers Sign in via approve, eve…
callumalpass Oct 11, 2026
a8b0380
Merge original extension sign-in recovery fix
callumalpass Oct 11, 2026
f6259f1
Pin original SDK 4c and preserve enabled sign-in gestures
callumalpass Oct 11, 2026
42d8ddf
Pin SDK 48b9f094
callumalpass Oct 11, 2026
db79d35
Space the buttons on the SDK connection screen
callumalpass Oct 12, 2026
0fcb055
Pin SDK 20c347ad
callumalpass Oct 12, 2026
8fa86c6
Pin SDK ac3bff98
callumalpass Oct 12, 2026
6d3071f
Keep the setup review open while setup applies
callumalpass Oct 12, 2026
ded01f3
Pin SDK 1f7b4f7a
callumalpass Oct 12, 2026
744c0bc
Pin SDK 8bc12525
callumalpass Oct 12, 2026
c364058
Pin SDK cf294bdb
callumalpass Oct 12, 2026
6b09b99
Pin SDK a965bd26
callumalpass Oct 12, 2026
bd6813d
Pin SDK 5f2c3d5d
callumalpass Oct 12, 2026
45526b1
Pin SDK 8b18a6c7
callumalpass Oct 12, 2026
2071482
Pin SDK 0f9a3614
callumalpass Oct 12, 2026
bbb7b48
Pin SDK 1dddb04c
callumalpass Oct 12, 2026
5c4d6d7
Pin SDK 5710ab13
callumalpass Oct 12, 2026
fba306b
Add a confirmed Remove source action to Reader
callumalpass Oct 12, 2026
3f1769c
Pin SDK 39ba2b8a
callumalpass Oct 12, 2026
ff1c1f3
Pin SDK 53643b29
callumalpass Oct 12, 2026
eb143dd
Pin SDK b1c47a32
callumalpass Oct 12, 2026
d5626fe
Pin SDK fb3308a8
callumalpass Oct 12, 2026
0307767
Use the shared collection screens from @mdbase-dev/ui
callumalpass Oct 12, 2026
e81fa65
Construct the mdbase entry without followVisibility
callumalpass Oct 12, 2026
5ed3db3
Pin SDK 4728a54c
callumalpass Oct 12, 2026
6ea4d2a
Follow page visibility again
callumalpass Oct 12, 2026
4e78d19
Pin @mdbase-dev/ui 5df37dc9
callumalpass Oct 12, 2026
58a49b0
Let the collection gate start the SDK entry
callumalpass Oct 12, 2026
c73f560
Leave unconfirmed writes to the SDK write ledger
callumalpass Oct 12, 2026
94d29ca
Test with the SDK's createTestApp
callumalpass Oct 12, 2026
ea4a196
Pin SDK 0aeaada6
callumalpass Oct 12, 2026
bd4daff
Pin SDK 47225957
callumalpass Oct 12, 2026
1cea552
Pin SDK fd0c4a67
callumalpass Oct 12, 2026
354049b
Serve a not-found page for unknown paths
callumalpass Oct 12, 2026
f5409b1
Show Offline or saved on this device instead of Connected
callumalpass Oct 12, 2026
d77f5fa
Reread the library when sources change in another tab, app or device
callumalpass Oct 12, 2026
1a28d62
Pin @mdbase-dev/ui 975fc4a0
callumalpass Oct 12, 2026
e1a2632
Pin SDK 013c7e56
callumalpass Oct 13, 2026
b36059f
Pin @mdbase-dev/ui 7ff0ce02
callumalpass Oct 13, 2026
1c1c53c
Save a retried highlight or note once
callumalpass Oct 13, 2026
7b288c8
Pin SDK 608bb0d5
callumalpass Oct 13, 2026
5d3af1e
Pin SDK a8fdbdce
callumalpass Oct 13, 2026
410c78d
Pin @mdbase-dev/ui 27fc243f
callumalpass Oct 13, 2026
44e3518
Hand the Private collection consent return to the waiting tab
callumalpass Oct 13, 2026
713a0e1
Pin SDK fb9691b0
callumalpass Oct 13, 2026
ed79389
Pin SDK d230034f
callumalpass Oct 13, 2026
e0460b3
Declare file actions for every record capability
callumalpass Oct 13, 2026
c714f23
Pin SDK 1629d11e
callumalpass Oct 13, 2026
0ba4baa
Show SDK storage risk beside Reader connection status
callumalpass Oct 13, 2026
a968a15
Clarify source titles and polish reading controls
callumalpass Oct 13, 2026
3bbc374
Pin SDK e00c1ff8
callumalpass Oct 13, 2026
cd0dc2d
Pin SDK af29edc9
callumalpass Oct 13, 2026
d909e99
Pin SDK 12c28ef1
callumalpass Oct 13, 2026
45120b5
Pin SDK 23a10ee4
callumalpass Oct 13, 2026
38a77e0
Pin SDK ca8f0682
callumalpass Oct 13, 2026
97df14c
fix: cache offline app shell
callumalpass Oct 13, 2026
677cb34
Pin SDK abc08f3e
callumalpass Oct 13, 2026
c4dc331
Pin SDK e4450c26
callumalpass Oct 13, 2026
4ea5409
Pin SDK 3491a619
callumalpass Oct 13, 2026
8ec56f6
fix: clarify source removal and retained annotation notes
callumalpass Oct 13, 2026
cbf1aaa
Keep phone source controls clear of install invitation
callumalpass Oct 13, 2026
2a18956
Pin SDK 6f2b1e51
callumalpass Oct 13, 2026
24a2ccc
Pin SDK 4c10b9a5
callumalpass Oct 13, 2026
5ea7e79
Pin SDK 5c7b9592
callumalpass Oct 13, 2026
2638cca
Pin SDK 6f14ebe1
callumalpass Oct 13, 2026
00c5fee
Pin SDK 0f5724ef
callumalpass Oct 13, 2026
23a243d
Pin SDK 106d36ff
callumalpass Oct 14, 2026
de9b256
Pin SDK f1aa67e3
callumalpass Oct 14, 2026
4b3d09c
Pin SDK bef3fe45
callumalpass Oct 14, 2026
dae27e9
Pin SDK 3f417b6d
callumalpass Oct 14, 2026
8414837
Pin unified SDK 72f8ea6 and defer Native popup close to its original …
callumalpass Oct 14, 2026
c986377
Pin SDK d3559aaa for declaration-bound Private setup
callumalpass Oct 15, 2026
914cf1d
Pin atomic two-pack Private setup fix and actionable SDK conflicts
callumalpass Oct 15, 2026
35d7bde
Record corrected LAB Private setup and edit reload acceptance
callumalpass Oct 15, 2026
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
10 changes: 10 additions & 0 deletions apps/extension/offscreen.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>mdbase Reader</title>
</head>
<body>
<script type="module" src="/src/offscreen.ts"></script>
</body>
</html>
2 changes: 1 addition & 1 deletion apps/extension/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
"typecheck": "tsc -p tsconfig.json"
},
"dependencies": {
"@mdbase-dev/ui": "0.1.0-beta.125",
"@mdbase-dev/ui": "file:../../vendor/mdbase-dev-ui-dev-9553ad66.tgz",
"@mdbase-reader/connect": "workspace:*",
"@mdbase-reader/core": "workspace:*",
"@mdbase-reader/platform": "workspace:*",
Expand Down
1 change: 1 addition & 0 deletions apps/extension/scripts/extension-manifest.d.mts
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@ export function manifestVersion(version?: string): {
version_name?: string;
};
export function extensionManifest(environment: ExtensionEnvironment): Record<string, unknown>;
export const storeExtensionId: string;
47 changes: 39 additions & 8 deletions apps/extension/scripts/extension-manifest.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,42 @@ import { readFileSync } from "node:fs";

import { readerDeploymentFor } from "../../reader/scripts/deployment-environment.mjs";

/** The Chrome Web Store build's ID; its origin is what Connect registers for the extension. */
export const storeExtensionId = "kimdfjefhbfgfecconmaiaaindjccidp";

/**
* The SDK's log service for each environment, which the extension reaches from its pages.
* Production has no SDK release trust yet, so it has no log origin to declare.
*/
const sdkLogOrigins = Object.freeze({
lab: "https://mdbase-next-log-lab-20261005.callumalpass.workers.dev",
});

const chromeExtensionId = /^[a-p]{32}$/u;

/**
* The extension targets exactly one mdbase environment, chosen at build time with
* `MDBASE_ENV` (lab by default), like Reader's own deployments.
* `MDBASE_ENV` (lab by default), like Reader's own deployments. A LAB build may also
* declare an unpacked build's ID (`MDBASE_EXTENSION_DEV_ID`, from chrome://extensions)
* so that build can register with LAB; other targets only declare the Web Store ID.
*/
export function extensionEnvironment(environment = process.env) {
const { target, deployment } = readerDeploymentFor(environment);
const devId = environment.MDBASE_EXTENSION_DEV_ID;
if (devId && (target !== "lab" || !chromeExtensionId.test(devId))) {
throw new Error("MDBASE_EXTENSION_DEV_ID must be a Chrome extension ID, for LAB builds only.");
}
return {
target,
label: target === "production" ? "" : target.toUpperCase(),
connectUrl: deployment.connectUrl,
logUrl: sdkLogOrigins[target] ?? null,
loopbackUrl: deployment.loopbackUrl,
readerOrigin: deployment.origin,
extensionOrigins: [
storeExtensionId,
...(devId && devId !== storeExtensionId ? [devId] : []),
].map((id) => `chrome-extension://${id}`),
};
}

Expand All @@ -39,7 +63,6 @@ const icons = Object.fromEntries(

export function extensionManifest(environment) {
const suffix = environment.label ? ` (${environment.label})` : "";
const loopback = new URL(environment.loopbackUrl);
return {
manifest_version: 3,
name: `mdbase Reader${suffix}`,
Expand All @@ -48,18 +71,26 @@ export function extensionManifest(environment) {
homepage_url: `${environment.readerOrigin}/`,
icons,
minimum_chrome_version: "123",
permissions: ["activeTab", "scripting", "storage", "contextMenus", "sidePanel"],
// The Connect API, and HTTPS pages: the side panel follows the active tab and saved
// pages are marked, which needs to read each page without a click on the toolbar.
host_permissions: [`${environment.connectUrl}/*`, "https://*/*"],
optional_host_permissions: [`${loopback.protocol}//${loopback.hostname}/*`],
permissions: ["activeTab", "scripting", "storage", "contextMenus", "sidePanel", "offscreen"],
// The mdbase control plane and log service the SDK talks to from the extension's
// pages, and HTTPS pages: the side panel follows the active tab and saved pages are
// marked, which needs to read each page without a click on the toolbar.
host_permissions: [
`${environment.connectUrl}/*`,
...(environment.logUrl ? [`${environment.logUrl}/*`] : []),
"https://*/*",
],
// The SDK's native runtime is WebAssembly.
content_security_policy: {
extension_pages: "script-src 'self' 'wasm-unsafe-eval'; object-src 'self'",
},
action: {
default_title: `Save to mdbase Reader${suffix}`,
default_icon: { 16: icons["16"], 32: icons["32"] },
},
// One panel per window; it follows the window's active tab.
side_panel: { default_path: "capture.html" },
// Classic, not a module: it loads page-status.js with importScripts() on demand.
// Classic, with no SDK: the SDK runtime lives in the extension's pages.
background: { service_worker: "background.js" },
options_ui: { page: "options.html", open_in_tab: true },
commands: {
Expand Down
16 changes: 9 additions & 7 deletions apps/extension/scripts/write-mdbase-manifest.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,18 @@ import { buildReaderManifest } from "../../reader/scripts/reader-manifest.mjs";

const root = resolve(import.meta.dirname, "..");
const reader = await buildReaderManifest();
const shared = { ...reader };
delete shared.homepage;
delete shared.icon;
delete shared.redirect_uris;
const environment = extensionEnvironment();
const sdkEnvironment = environment.target === "lab" ? "lab" : "production";
// A web app manifest whose pages are the extension's: Connect registers the extension's
// exact chrome-extension origins under installation_origins for this environment.
const manifest = {
...shared,
distribution: "portable",
...reader,
id: "dev.mdbase.reader.extension",
name: "mdbase Reader browser extension",
project_url: `${extensionEnvironment().readerOrigin}/`,
homepage: `${environment.readerOrigin}/`,
icon: `${environment.readerOrigin}/favicon.svg`,
redirect_uris: [`${environment.readerOrigin}/`],
installation_origins: { [sdkEnvironment]: { "app-runtime": environment.extensionOrigins } },
};
const target = resolve(root, "src/generated/mdbase-app.json");
await mkdir(resolve(target, ".."), { recursive: true });
Expand Down
58 changes: 31 additions & 27 deletions apps/extension/src/CaptureApp.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import type { PanelTab } from "./PanelTabs.js";

function markup(changes: Partial<ExtensionCaptureController> = {}, tab?: PanelTab): string {
const controller = {
snapshot: { status: "unselected", connections: [] },
snapshot: { status: "signed_out", collections: [] },
capture: {
kind: "html",
pageTitle: "[test] Article",
Expand Down Expand Up @@ -52,8 +52,8 @@ it("asks a new user only to connect, with editable metadata and no status noise"
it("picks among connected collections with Reader's select", () => {
const html = markup({
snapshot: {
status: "unselected",
connections: [{ collectionId: "c1", displayName: "[test] Papers" }],
status: "choosing",
collections: [{ collectionId: "c1", displayName: "[test] Papers" }],
} as unknown as ExtensionCaptureController["snapshot"],
});
expect(html).toContain("Save to collection");
Expand All @@ -67,7 +67,7 @@ it("links the brand to the Reader library on the selected collection", () => {
snapshot: {
status: "ready",
collectionId: "c1",
connections: [{ collectionId: "c1", displayName: "[test] Papers" }],
collections: [{ collectionId: "c1", displayName: "[test] Papers" }],
} as unknown as ExtensionCaptureController["snapshot"],
});
expect(html).toMatch(
Expand Down Expand Up @@ -144,7 +144,7 @@ it("names the connected collection in the header, with its controls behind it",
snapshot: {
status: "ready",
collectionId: "c1",
connections: [{ collectionId: "c1", displayName: "[test] Papers" }],
collections: [{ collectionId: "c1", displayName: "[test] Papers" }],
} as unknown as ExtensionCaptureController["snapshot"],
});
expect(html).toMatch(
Expand All @@ -165,25 +165,26 @@ it("shows one status line at a time, with problems taking precedence", () => {
expect(failing).toContain("offline");
expect(failing).not.toContain("Highlight saved.");
});
it.each(["unavailable", "start_failed", "blocked"])(
"offers connection retry, not reapproval, for %s",
(status) => {
const html = markup({
snapshot: {
status,
collectionId: "c1",
connections: [{ collectionId: "c1", displayName: "Papers" }],
problem: { message: "Temporarily blocked" },
} as unknown as ExtensionCaptureController["snapshot"],
problem: "Request timed out",
problemKind: "save",
saveAttempted: true,
});
expect(html).toContain("Retry connection");
expect(html).not.toContain("approve access again");
expect(html).not.toContain("Review access");
},
);
it("offers the SDK's recovery, not reapproval, when the collection has a problem", () => {
const html = markup({
snapshot: {
status: "problem",
collectionId: "c1",
collections: [{ collectionId: "c1", displayName: "Papers" }],
problem: {
title: "Can't reach mdbase",
message: "Check your connection.",
action: "retry",
actionLabel: "Try again",
},
} as unknown as ExtensionCaptureController["snapshot"],
problem: "Request timed out",
problemKind: "save",
saveAttempted: true,
});
expect(html).toContain("Try again");
expect(html).not.toContain("Review access");
});
it("does not display stale write progress after a successful save", () => {
const html = markup({
status: "saved",
Expand Down Expand Up @@ -350,7 +351,7 @@ it("asks for a title from the save action when the draft has none", () => {
snapshot: {
status: "ready",
collectionId: "c1",
connections: [{ collectionId: "c1", displayName: "[test] Papers" }],
collections: [{ collectionId: "c1", displayName: "[test] Papers" }],
} as unknown as ExtensionCaptureController["snapshot"],
draft: {
title: " ",
Expand Down Expand Up @@ -459,14 +460,17 @@ it("titles the page as it will be saved, not as the tab names it", () => {
});
it("says why a selected passage cannot be saved yet", () => {
const html = markup({
snapshot: { status: "unselected", connections: [] },
snapshot: {
status: "signed_out",
collections: [],
} as unknown as ExtensionCaptureController["snapshot"],
capture: {
kind: "html",
pageTitle: "[test] Article",
canonicalUrl: "https://example.com/",
selection: { exact: "a passage" },
} as ExtensionCaptureController["capture"],
});
expect(html).toContain("Connect a collection before saving.");
expect(html).toContain("Sign in to mdbase before saving.");
expect(html).toMatch(/class="swatch swatch-yellow[^"]*"[^>]*disabled=""/u);
});
2 changes: 1 addition & 1 deletion apps/extension/src/CaptureApp.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ export function CaptureApp({
<main className="capture-shell">
<ExtensionHeader
collectionId={
"collectionId" in controller.snapshot ? controller.snapshot.collectionId : null
controller.snapshot.status === "ready" ? controller.snapshot.collectionId : null
}
collection={
collection
Expand Down
Loading