Archilyzer · Source

archilyzer

Archilyzer
git clone https://archilyzer.pages.dev/source/archilyzer.git
Log | Files | Refs | README | LICENSE

commit 74cdd1fa3441d613c961e10974159b5d956fdff9
parent 69e9f1b18e0d484986efcd6bf92890dae6cfec2f
Author: I Mean I'm Just Saying <imeanimjustsaying@kiwifarms.st>
Date:   Thu,  1 Oct 2026 11:35:22 -0400

plans: slice XL as shipped — the Connect window without automation signals, the browser-login source, what was measured; FACTS; the editor changelog

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

Diffstat:
Meditor/CHANGELOG.md | 1+
Mplans/FACTS.md | 70++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mplans/release-16.md | 165++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
3 files changed, 235 insertions(+), 1 deletion(-)

diff --git a/editor/CHANGELOG.md b/editor/CHANGELOG.md @@ -14,6 +14,7 @@ - **A job cancelled before it started now stays cancelled.** Its record on disk kept saying "queued", so a restart could put a job you had just cancelled back in its queue, and a clip fetch cancelled while waiting could be reported as still queued. Jobs still waiting when the editor shuts down are handled as before: the next start settles or re-queues them. - **A site's Charts tab gives a sixth series its own colour.** The dashboard's charts coloured their series from five colours and started again at the sixth, so a chart broken down by six or more channels drew the sixth in the first one's colour. The sixth now takes the palette's sixth colour, and each from the seventh on a hue of its own; the first five are unchanged. The published sites' charts get the same change with their next build. - **A form whose save is refused keeps what you typed.** Every editor form put its plain fields back to the stored values when its save was refused — a site's ID rejected, a page size out of range, a slug already taken — so everything typed had to be typed again. A refused save now leaves every field as you left it, beside the reason: **Settings**; a site's form (new and existing); the hub's config on `/sites`; **Cut release**; a channel's form (new and **Configure**), **Rename** and **Delete**; a video's **Delete directory**; **Drive health timing** on `/storage`; the backup config on `/saved-videos`; the sync operation's controls; the **Digest**, **Diarization**, **Speaker attribution** and **Speaker work lane** settings; and the worker list on `/workers`. A save that succeeds behaves as before, with one difference you may notice: a drop-down, and a checkbox or choice that the page tracks as you change it (a cadence, a worker's **Enabled**, a social link's **Keep in header**, a site membership, a site's accent), now shows what was saved. A form's own drop-downs used to go back to what the page had loaded with until a reload, and a second save from the same page sent that old choice again; the others went back until the page next refreshed itself (every 5 seconds by default). +- **Connecting an X account opens your own browser, and the X fetchers can use your everyday browser's X login instead.** **Settings → X account session → Connect X account** used to open Playwright's bundled Chromium with its automation signals on (the "controlled by automated test software" bar, `navigator.webdriver`): Google's sign-in refused it and X's own login form stalled in it. It now opens your Chromium or Chrome when one is installed (`ARCHILYZER_X_BROWSER` names another; Playwright's bundled Chromium otherwise), without those signals. Google's sign-in may still refuse an embedded browser; X's password login is the reliable path. A new **Login source** choice (`social.x.cookieSource` in `settings.json`) says where the X fetchers' login comes from: **Browser login** hands gallery-dl `--cookies-from-browser` with your `cookiesFromBrowser` on every fetch, so the login lasts as long as you stay logged in to x.com in that browser and no window is needed; **Connected profile** is the session broker, as before. Left on **Automatic**, it is the browser login when `cookiesFromBrowser` is set and no profile is connected, and the profile otherwise. **Check** says which source is in use, whether an X login is visible in it and when it was last used (the browser's cookies are read from a private copy, never written; this reads Firefox's, and gallery-dl reads Chromium's itself). Needs a rebuild and restart of the editor. ## [0.11.0] - 2026-09-30 - **Transcripts that arrived after a video was first seen are counted.** The stats behind the homepage, the hub and every site's charts were cached per video and refreshed only when the video's metadata changed, so a transcript that came later — a Whisper run days after the download, or a video downloaded after the last index build — never reached them, and a video with YouTube captions alone had no transcription date. Counts and charts were low; the homepage could show a site with 0 transcripts, 0 channels and 0 hours while it served its videos. A stat is now also redone whenever the index re-reads the video, every transcript has a date, and a captioned video is dated by when its captions arrived rather than by a later Normalize run, so its place on "Transcribed over time" can move. **After updating, rebuild and restart the editor before anything else:** until then, **Build stats dataset** runs the old code and would undo the new stats, while a site, hub or homepage build already runs the new code — and the first stats build of any kind re-reads every video once (about 10–30 minutes on a large archive; it can be stopped and picks up where it stopped). Then build the index, the stats, the homepage, the hub, and the sites. diff --git a/plans/FACTS.md b/plans/FACTS.md @@ -8090,3 +8090,73 @@ source mirror (homepage)". Anchors are at the branch. (a read never throws). `forms-keep-input.spec.ts` `withSettingsUnwritable` restores the file in a `finally`. A background writer in that window fails the same way, so the spec keeps it to one submit. + +## Connecting an X account (verified 2026-10-01, branch `r16/x-login`) + +- **Why Google refused the Connect window: the automation signals.** Measured on this box under + Xvfb (headed, no window on the operator's display; the slice's `xl-exp/`), Playwright 1.59.1 + driving system Chromium 153 and the bundled build: + - Playwright's defaults: `navigator.webdriver === true` and the bar "Chrome is being controlled + by automated test software" (that is `--enable-automation`). + - `ignoreDefaultArgs: ["--enable-automation"]` alone: the bar goes, **`navigator.webdriver` stays + true** — Playwright's `--remote-debugging-pipe` sets it. `--disable-blink-features= + AutomationControlled` is what turns it off (false, headed and headless). + - A system Chrome then draws "You are using an unsupported command-line flag" for + `--disable-blink-features=AutomationControlled`, and for Playwright's default `--no-sandbox`. + `--test-type` (what ChromeDriver passes) suppresses the first; `chromiumSandbox: true` removes + the second (the sandbox starts on this box for both builds). The bundled Chrome for Testing + draws neither (it honours Playwright's `--disable-infobars`). + - Not hidden by any of this: the debugging pipe itself, and the headless user agent + (`HeadlessChrome/…`). Google's sign-in may still refuse; X's password login is the reliable path. +- **The launch options are one pure builder**, `buildXBrowserLaunchOptions` + (`common/social/xBrowser.ts`): `ignoreDefaultArgs: ["--enable-automation"]` ONLY, `args` + `--disable-blink-features=AutomationControlled` and `--test-type`, the sandbox on for the headed + window (it retries without when the host cannot start one). **Never `ignoreDefaultArgs: true`:** + it also drops `--password-store=basic`, and a system Chromium would then encrypt the profile's + cookies with the desktop keyring's key, which the bundled headless build that refreshes the jar + cannot read. +- **Which browser opens.** `ARCHILYZER_X_BROWSER` (a path, or a name on PATH; one that is not an + executable refuses the connect), else the first of `chromium`, `google-chrome`, + `google-chrome-stable`, `chrome` on PATH, else Playwright's bundled Chromium. Connect logs which + (`[x-session] Opening Chromium 153.0.8010.47 Arch Linux at /usr/bin/chromium (found on PATH)…`). +- **The bundled build is chromium-1217, Chrome for Testing 147.0.7727.15** (and its headless shell, + the same revision): `playwright-core@1.59.1`'s `browsers.json`. `~/.cache/ms-playwright` also holds + chromium-1234 (151) and two older revisions; this Playwright does not use them. +- **The profile/executable pairing.** A connect writes `archilyzer-browser.json` into the profile + dir (`{ executablePath | null, version, recordedAt }`). The headless refresh and the Playwright + fallback fetcher open the profile with the bundled build (`launchXProfile`) and fall back to the + recorded executable only when the bundled launch throws. Measured: a profile written by system + Chromium 153 (a cookie added, headed and headless) opens in the bundled headless shell 147 with the + cookie's value readable; `Last Version` stays `153.0.8010.47`; 153 re-opens it after. So the + fallback is a guard, not the common path. +- **The X login source, `social.x.cookieSource`** (`common/social/xCookieSource.ts`, pure). + `"browser"` | `"profile"`; absent = **resolved at read time, never stored**: `"browser"` when + `cookiesFromBrowser` is set and no profile is connected (connected = the broker's exported jar + carries an `auth_token`, `readXSessionStatus().looksAuthenticated`), else `"profile"`. A stored + choice always wins. The fetch resolves it per channel against the channel's own + `cookiesFromBrowser` over the global one (`fetchPosts` → `resolveXCookieSourceFor`). + **The source, not `cookieMode`, governs the X fetchers**: the browser source passes the spec + whatever the mode; the profile source keeps the old order (the jar, else the `"always"`-mode + spec, else a guest run). +- **gallery-dl 1.32.9** takes `--cookies-from-browser BROWSER[/DOMAIN][+KEYRING][:PROFILE][::CONTAINER]` + (yt-dlp's syntax plus `/DOMAIN`) and reads every browser it supports, Chromium's encrypted store + included, on each run. The spec is passed verbatim (`galleryDlCookieChoice`). +- **Reading Firefox's store ourselves** (`common/social/xBrowserLogin.ts`): for the Playwright + fallback and the Settings "Check". The store is found as gallery-dl and yt-dlp find it — a + profile path or name from the spec, else the most recently modified `cookies.sqlite` under + `~/.config/mozilla/firefox`, `~/.mozilla/firefox`, Snap, Flatpak, macOS (two levels deep). + `cookies.sqlite` **and its `-wal`** are copied to a private `mkdtemp` dir (a running Firefox + keeps fresh rows in the WAL until a checkpoint; a fixture held open in WAL mode proves it), read + there, and removed; only X's rows are selected; the profile is never opened in place. A + container (`::NAME`) is a `userContextId` from `containers.json` (`::none` = outside every + container), as in yt-dlp. `expiry` is seconds (read as ms when too large for seconds); + `lastAccessed` is PRTime (µs). The Chromium family is NOT read here (its values are encrypted + with a keyring key): the readers say so and gallery-dl reads it. +- **`node:sqlite`** (Node 22.23 here; unflagged since 22.13, an ExperimentalWarning once per process) + is loaded at run time through an assembled specifier with `webpackIgnore` + (`common/social/nodeSqlite.ts`), like `playwrightRuntime.ts`: the editor's Turbopack build has + nothing to resolve, and the repo's `@types/node` (20) has no types for it, so they are structural. +- **The e2e never reads a real browser.** `x-session.spec.ts` writes a Firefox store under + `test-transcripts/` (`common/social/__fixtures__/firefoxCookieStore.ts`, imported by relative path) + and points `cookiesFromBrowser` at it as `firefox:<abs path>` — a profile PATH in the spec, so no + home directory is searched. diff --git a/plans/release-16.md b/plans/release-16.md @@ -25,7 +25,7 @@ slice's prompt carries its ruling, and this record carries what was built. Rules | DX | `r16/research-setup` | The research-only setup (source → `pnpm install` → `claude mcp add archilyzer` → `/ask`) in one place, the homepage's AI and MCP doc; the sites' and the hub's Use-with-AI page removed and its links pointed at the doc; `README.md` §1/§4 and `mcp/README.md` their own copies (as amended) | `homepage/content/docs/ai-and-mcp.md`, `export/app/use-with-ai/` (removed), the Use with AI links (`export/app/components/{Header,MobileMenu,Footer}.tsx`, `export/app/(workspace)/ask/page.tsx`), `common/lib/{project,corpus}.ts` + `common/bin/compose-site.ts` (what named the page), `mcp/README.md`, `README.md` §1/§4 (wording only), `homepage/e2e/docs.spec.ts`, `export/e2e{,-hub}/use-with-ai-link.spec.ts` and the specs that visited the page | | FK | `r16/forms-keep-input` | Every editor form keeps what was typed when its action fails: actions return the submitted values with the error, the shared field helpers seed from them | new `editor/app/lib/formState.ts` + test; `editor/app/components/forms/Field.tsx` and the local `Field`s in `SiteForm.tsx`, `ChannelForm.tsx`; every action that returns `{ok:false,error}`/`{error}` (sites, settings, operations/settingsActions, scheduler, storage, homepageActions, cutReleaseAction, channels, videoActions); the 15 forms the ruling lists; e2e `forms-keep-input.spec.ts` (new) + the existing `sites-crud`, `settings`, `channels` specs; records: `plans/FACTS.md`. As shipped, also `editor/app/components/forms/Controlled.tsx` (new) and one tag swap each in `DurationField`, `SocialLinksField`, `SiteMembershipsSection`, `WorkersField` ("Slice FK, as shipped") | | RM | `r16/move-holds-writers` | A media move holds the channel's writers and mirrors its copy, so a transcription or download during the move cannot fail it | `common/controller/relocateChannelMedia.ts` (+ the saved-video mover if it shares the code) + tests; the lane runners' per-channel skip (`common/controller/autoRunner.ts`, the backfill/digest/normalize/clip-fetch entry points, `common/lib/channelMediaHold.ts`); `common/jobs/jobKinds.ts` (`needsMedia`); the channel page's Storage panel and the rack's reason text; `editor/e2e/relocate*.spec.ts`; records: `plans/FACTS.md` | -| XL | `r16/x-login` | Connecting an X account works: the Connect window is the operator's own browser without automation signals, and the fetchers can use the operator's browser login directly (`cookiesFromBrowser`) with no window at all | `common/social/{xSessionBroker,xGalleryDlFetcher,fetchers,playwrightRuntime}.ts` + tests; `common/lib/settingsSchema.ts` (one key) + `SETTINGS.md`; `editor/app/settings/{xSessionActions.ts,components/XSessionSection.tsx}`; `editor/e2e/settings*.spec.ts`; records: `plans/FACTS.md`, `ENVIRONMENT.md` if an env var is added | +| XL | `r16/x-login` | Connecting an X account works: the Connect window is the operator's own browser without automation signals, and the fetchers can use the operator's browser login directly (`cookiesFromBrowser`) with no window at all | `common/social/{xSessionBroker,xGalleryDlFetcher,fetchers,playwrightRuntime}.ts` + tests; `common/lib/settingsSchema.ts` (one key) + `SETTINGS.md`; `editor/app/settings/{xSessionActions.ts,components/XSessionSection.tsx}`; `editor/e2e/settings*.spec.ts`; records: `plans/FACTS.md`, `ENVIRONMENT.md` if an env var is added. As shipped, also `common/social/{xBrowser,xCookieSource,xBrowserLogin,nodeSqlite}.ts` and `__fixtures__/firefoxCookieStore.ts` (new), `xPlaywrightFetcher.ts`, `common/controller/fetchPosts.ts` (the source per channel), `common/lib/{settingsDocs,envVars}.ts`, `editor/app/settings/page.tsx`, and `editor/e2e/x-session.spec.ts` — the spec that covers the section ("Slice XL, as shipped") | ## Slice CK — the ruling (2026-09-30) @@ -785,6 +785,169 @@ FK row. `new-channel-onboarding`, `social-channel`, `sites-crud`, `settings`: **70 passed**, 0 failed, 5.6 min. The full suite was not re-run, as the parent directed. +### Slice XL, as shipped — connecting an X account works (2026-10-01) + +Branch `r16/x-login` off `main` `a2229d68`, worktree `~/Projects/r12-source-mirror` (editor 5101, +test 5111, export 5110), one Opus implementer. Scratch files `xl-*` in the job's `tmp`; the +measurements behind the launch options are `xl-exp/` (two probe scripts, their screenshots, and a +smoke of the slice's own modules). The ruling is above ("Slice XL — the ruling"). + +**Measured first** — Playwright 1.59.1 driving system Chromium 153 and the bundled build, headed +under Xvfb (never on the operator's display) and headless, on scratch profiles; no page ever +loaded x.com or Google: + +| Launch | `navigator.webdriver` | The window's bar | +|---|---|---| +| Playwright's defaults, system Chromium | true | "Chrome is being controlled by automated test software" | +| `ignoreDefaultArgs: ["--enable-automation"]`, sandbox on | **true** — the debugging pipe sets it | none | +| + `--disable-blink-features=AutomationControlled`, Playwright's `--no-sandbox` | false | "You are using an unsupported command-line flag: --no-sandbox" | +| the same, sandbox on | false | "… unsupported command-line flag: --disable-blink-features=AutomationControlled" | +| + `--test-type` | false | none | +| the bundled Chrome for Testing, the same options, sandbox on or off | false | none | + +So the blink flag is what turns `navigator.webdriver` off, and `--test-type` plus the sandbox is +what keeps a system Chrome's window free of a warning bar. **The bundled build is chromium-1217, +Chrome for Testing 147** (`playwright-core@1.59.1`'s `browsers.json`), not the cache's +chromium-1234 (151), which this Playwright does not use. **A profile written by system Chromium 153 +opens in the bundled headless shell 147 with its cookies readable** (a cookie added in 153, read back +by value in 147; `Last Version` stays 153; 153 re-opens it after), so the refresh keeps the bundled +build and the recorded executable is a fallback, not the common path. + +**What it does.** +- **The Connect window is the operator's own browser without the automation signals** + (`common/social/xBrowser.ts`, used by `connectXAccount`): `ARCHILYZER_X_BROWSER` (a path or a name + on PATH; one that is not an executable refuses the connect — it never opens another browser), + else the first of `chromium`, `google-chrome`, `google-chrome-stable`, `chrome` on PATH, else the + bundled Chromium. One pure options builder, `buildXBrowserLaunchOptions`: + `ignoreDefaultArgs: ["--enable-automation"]` only (never `true`: that would drop + `--password-store=basic` and a system Chromium would encrypt the profile's cookies with the desktop + keyring's key, unreadable by the bundled refresh), `--disable-blink-features=AutomationControlled`, + `--test-type`, the sandbox on for the headed window (retried without it, and logged, when the host + cannot start one). Connect logs the browser (`[x-session] Opening Chromium 153.0.8010.47 Arch + Linux at /usr/bin/chromium (found on PATH). …`) and writes it into the profile + (`archilyzer-browser.json`); its success note names it. +- **The profile opens headless through one function**, `launchXProfile` (the refresh and the + Playwright fallback fetcher): the bundled build with the same signals dropped, and the recorded + executable only when the bundled launch throws. +- **The login source, `social.x.cookieSource`** (`common/social/xCookieSource.ts`, pure; the one new + settings key, `SETTINGS.md` and `settings.json.example` regenerated): `"browser"` | `"profile"`, + absent by default and **resolved at read time** — `"browser"` when `cookiesFromBrowser` is set and + no profile is connected (no exported jar carrying an `auth_token`), else `"profile"`. `fetchPosts` + resolves it per channel (the channel's own `cookiesFromBrowser` over the global) and hands the X + fetchers `cookieSource` and `browserCookies` (`PostFetchInput`, `common/social/fetchers.ts`). + **The source, not `cookieMode`, governs X**: the browser spec is passed whatever the mode. + - **gallery-dl** (`galleryDlCookieChoice`, pure): browser → `--cookies-from-browser <spec>` verbatim + (gallery-dl's `BROWSER[/DOMAIN][+KEYRING][:PROFILE][::CONTAINER]`), never the jar, fresh on every + run; browser with no spec → a guest run that says why; profile → the jar, else the + `"always"`-mode spec, else a guest (the order before the choice existed). The job log names the + source each run. + - **The Playwright fallback**: browser → a fresh headless context carrying the cookies + `xCookiesFromBrowser` reads now; a browser it cannot read refuses by name instead of running + logged out. Profile → `launchXProfile`, as before. + - **Nitter** uses no X login and is unchanged. +- **`xCookiesFromBrowser(spec)` and `readXLoginStatus(paths, settings)`** + (`common/social/xBrowserLogin.ts`). The read takes the resolved SPEC, not the settings, so a + channel's own `cookiesFromBrowser` holds. **Firefox only, read-only**: the store is found as + gallery-dl and yt-dlp find it (a profile path or name, else the most recently modified + `cookies.sqlite` under the Firefox roots), `cookies.sqlite` and its `-wal` are copied into a private + `mkdtemp` dir, X's rows alone are selected, the copy is removed; a `::CONTAINER` is honoured. + **Chromium's encrypted store is not read here** — gallery-dl (and yt-dlp) read it; for such a spec + the Check and the fallback say so. The status: the source in use and whether it was chosen, whether + an `auth_token` for x.com is visible (`null` when the source cannot be read), and when it was last + seen — the browser's own `lastAccessed` for that cookie, or the jar's export time for the profile. + `node:sqlite` is loaded at run time (`common/social/nodeSqlite.ts`). +- **The Settings section** (`XSessionSection.tsx`, `xSessionActions.ts`, `settings/page.tsx`): the + intro names the two sources; a **Login source** select (Automatic — now: …, Browser login, + Connected profile) saved on change through `saveSettings` (Automatic stores `social.x` empty); the + source in use; **Check**, whose result is one line; the Connect text says the window is the + operator's own browser, that Google's sign-in may still refuse an embedded browser, and that X's + password login is the reliable path. Every session action also returns the source in use + (connecting or forgetting a profile can move the default). +- `ARCHILYZER_X_BROWSER` is declared in `common/lib/envVars.ts` (runtime) and `ENVIRONMENT.md` + regenerated. + +**New accessible names (contracts from here on):** `x cookie source` (the select — `getByLabel` +needs `{ exact: true }`, the next one contains it), `x cookie source in use`, `check x login`, +`x login status`. Unchanged: `x session state`, `connect x account`, `refresh x cookies`, +`clear x session`, the `data-x-session` hook and the `role="status"` / `role="alert"` lines. + +**Commits** + +| Commit | What | +|---|---| +| `cfb6dd10` | `common:` the Connect browser and its options, the login source, the browser-login reader and status, the fetchers, the `social` settings block; SETTINGS.md, settings.json.example, ENVIRONMENT.md | +| `a266b18a` | `editor:` the X account session's source select, Check and Connect text; the actions; `x-session.spec.ts` | +| this commit | `plans:` this section; FACTS; the editor changelog | + +#### Gates (logs `$T/xl-*.log`) + +- **tsc** (all workspaces) clean, 126 s, at `a266b18a`'s tree — after deleting the worktree's stale + `export/.next/types` (an old build's, still naming the removed `/use-with-ai` page). +- **common:** 2,439/2,439, 113 s — the new `xBrowser.test.ts` (9), `xCookieSource.test.ts` (7), + `xBrowserLogin.test.ts` (11), `xGalleryDlFetcher.test.ts` (7) and one more `settingsSchema` case; + `settingsDocs`, `envVars` (both generated files) and `architecture` green. + `archilyzer docs files --check` clean. **Editor unit:** 109/109 (unchanged). +- **Build:** the capped editor build with the corpus linked (the worktree's own `transcripts/` set + aside, `ln -sT` the primary's, `systemd-run --scope -p MemoryMax=5G`, `pnpm --filter editor exec + next build`, the link removed and the directory put back): exit 0, 73 s, 1.64 GB peak RSS. +- **Smoke of the slice's modules** (`xl-exp/smoke.mts`, under Xvfb, a scratch corpus, no + navigation): `findXBrowser` picked `/usr/bin/chromium`; the Connect options gave + `navigator.webdriver === false`; the record was written; `launchXProfile` opened the profile with + the bundled headless build and read the cookie back by value, `navigator.webdriver === false`. +- **Numbers tool:** none. + + | Run | At | Specs | Result | + |---|---|---|---| + | 1 | `a266b18a` | `x-session`, `settings`, `cookies-mode`, `forms-keep-input`, `social-channel`, `social-fetcher-picker` | XL_E2E_1 | + +**Not tested, and cannot be here:** a real Google or X login in the Connect window, and gallery-dl +or the fallback against x.com — the suite never loads X, and no test reads a real browser profile. +The operator verifies by hand after the restart (below). + +#### Found and left + +- **Google may still refuse.** The debugging pipe Playwright drives the window through is still + there, and a sign-in page can look for more than `navigator.webdriver`. The section says so and + points at X's password login. +- **The headless paths still say `HeadlessChrome`** in their user agent (the refresh and the + Playwright fallback); only `navigator.webdriver` is off there. +- **Chromium-family specs are read by gallery-dl only.** With `cookiesFromBrowser` naming chrome or + chromium, the browser source works for gallery-dl; the Check reports "cannot be read here" (`null`, + not "no login") and the Playwright fallback refuses. Reading Chromium's store would mean the + keyring decryption gallery-dl and yt-dlp already carry. +- **The default reads "connected" as "an exported jar with an auth_token".** A profile that is + logged in but has never exported (Connect closed before the jar was written) counts as not + connected, so the default stays on the browser until a refresh or a connect writes the jar. +- **The ExperimentalWarning for `node:sqlite`** prints once per editor process, the first time the + Check or the fallback reads a browser store. +- **Not run against the operator's own Firefox.** The session's tool permissions refused reading + the operator's real Firefox profile, so whether `firefox` resolves to the profile the operator + logs in to X with is the first thing the Check shows after the restart. + +#### Decisions the operator could overturn + +| What I did | The alternative | +|---|---| +| `--test-type` in the launch args and the sandbox on for the headed window, so a system Chrome draws no warning bar | The ruling's two flags alone: the window shows "unsupported command-line flag" for the blink flag (or for `--no-sandbox`) | +| The refresh and the fallback drop the same signals headless (`launchXProfile`) | Leave them on Playwright's defaults; the ruling named only the Connect window | +| `ARCHILYZER_X_BROWSER` is a runtime variable read in `xBrowser.ts` | A `paths` entry in `getPaths()` beside `GALLERY_DL_BIN` (it would add a field every `Paths` literal must carry) | +| "Automatic" is a third option in the select, storing nothing | Two options only: the read-time default would apply until the first choice and never again | +| The browser source passes the spec whatever `cookieMode` says | Gate it on `cookieMode` like yt-dlp's cookies (`defer` would then run X as a guest) | +| The browser source's Playwright fallback refuses a browser it cannot read | Fall back to the profile, or run logged out | +| `xCookiesFromBrowser(spec)` takes the resolved spec | The prompt's `(paths, settings)`: a channel's own `cookiesFromBrowser` would be ignored | + +#### After the restart, by hand (the operator) + +1. `/settings` → X account session: **In use** should read `Browser login (firefox) (automatic)` + (the live `settings.json` has `cookiesFromBrowser: "firefox"` and no exported jar). **Check**: an + X login visible in firefox, with when its `auth_token` was last used. If it says none, log in to + x.com in that Firefox profile and Check again. +2. One X channel's **Fetch posts**: the job log says `X login: the browser (firefox), read by + gallery-dl.` before gallery-dl runs. +3. Optionally **Connect X account**: the window should be Chromium 153, with no "controlled by + automated test software" bar; log in with X's username and password (not Google), close the + window. The source stays as chosen; on Automatic it moves to the connected profile. + ## Rollout Both slices are export- and homepage-side; the editor and umtool are not rebuilt for this release.