Skip to content

Commit 9931337

Browse files
committed
merge(dev): resolve isolate settle and ACL stub conflicts
Keep 750ms Windows Worker OS-join settle and non-Windows hammer skips for Bun 1.3.14 isolate segfaults; take tip's scoped management-auth icacls fakes.
2 parents f1fa464 + 63735af commit 9931337

43 files changed

Lines changed: 1233 additions & 355 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/ci.yml

Lines changed: 29 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -47,20 +47,31 @@ concurrency:
4747
jobs:
4848
# Which Windows runner this run is allowed to use.
4949
#
50-
# The self-hosted Windows box is a maintainer's PERSONAL machine on a home
51-
# network, reachable over Tailscale. Anything that runs on it runs as a local
52-
# user with that user's filesystem and LAN in reach, so the only question that
53-
# matters is whether the commit is trusted BEFORE a runner ever sees it.
50+
# READ THIS BEFORE TREATING IT AS A SECURITY BOUNDARY: it is not one.
5451
#
55-
# `push` on dev/main/preview is trusted: reaching those branches requires the
56-
# push permission, which only the maintainers in MAINTAINERS.md hold.
57-
# `pull_request` is NOT trusted, and no author check can make it so — the
58-
# workflow file itself comes from the PR head, so any `if:` guard written here
59-
# is deleted by the same patch it is meant to stop. Fork PRs therefore stay on
60-
# GitHub-hosted runners, unconditionally.
52+
# On `pull_request` this workflow is loaded from the PR head, so the `case`
53+
# below is owned by the proposed patch exactly like an `if:` guard would be.
54+
# A hostile PR can delete the branch and hardcode the self-hosted labels into
55+
# `$GITHUB_OUTPUT`, and `runs-on` will honour it. That this job runs on
56+
# `ubuntu-latest` changes nothing — the untrusted part is its OUTPUT, not its
57+
# host. `.github/workflows/ci.yml` is in this workflow's `pull_request.paths`,
58+
# so such an edit triggers its own run.
6159
#
62-
# `workflow_dispatch` is the maintainer escape hatch: dispatch requires write
63-
# access, so a maintainer can deliberately aim a branch at the home box.
60+
# What actually keeps untrusted code off a self-hosted runner lives OUTSIDE
61+
# this file, where a PR cannot reach it: the fork-PR approval policy
62+
# (`all_external_contributors`) and the judgement of whoever clicks approve.
63+
# Runner groups would be the other lever, but they are an organisation
64+
# feature and this repository is user-owned, so the approval policy is the
65+
# only one available here. GitHub's own guidance is to avoid self-hosted
66+
# runners on public repositories for this reason.
67+
#
68+
# So read the routing below as a COST control that keeps honest pull requests
69+
# on GitHub-hosted runners, not as a guarantee about hostile ones.
70+
#
71+
# `push` on dev/main/preview requires the push permission, and
72+
# `workflow_dispatch` requires write access, so both carry a trusted author.
73+
# A trusted author is not audited code: merging a contributor PR into `dev`
74+
# fires `push`, and its dependencies and postinstall hooks then run here.
6475
select-windows-runner:
6576
name: select windows runner
6677
runs-on: ubuntu-latest
@@ -85,10 +96,12 @@ jobs:
8596
push|workflow_dispatch) trusted=yes ;;
8697
esac
8798
88-
# Repository variable OCX_SELF_HOSTED_WINDOWS acts as a kill switch. When
89-
# it is anything other than `1` — including unset, which is the state
90-
# before the runner exists — every job falls back to windows-latest and
91-
# CI keeps working with the box powered off.
99+
# Repository variable OCX_SELF_HOSTED_WINDOWS is an OPERATIONAL switch,
100+
# not a security control: a PR that rewrites this script ignores it for
101+
# the same reason it ignores the event check above. Its job is to keep CI
102+
# working when the box is off or busy. Anything other than `1` —
103+
# including unset, the state before a runner exists — falls back to
104+
# windows-latest.
92105
if [ "$trusted" = "yes" ] && [ "${USE_SELF_HOSTED:-}" = "1" ]; then
93106
echo 'runner=["self-hosted","Windows","X64","ocx-home"]' >> "$GITHUB_OUTPUT"
94107
echo 'label=self-hosted (ocx-home)' >> "$GITHUB_OUTPUT"

gui/src/client-resource.ts

Lines changed: 38 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,12 @@ type Store<T> = {
4040
/** Store-level visibilitychange handler, installed only while this store polls. */
4141
visibilityListener: (() => void) | null;
4242
generation: number;
43+
/**
44+
* Set when `setClientResourceData` publishes while nobody is subscribed (session-cache
45+
* seed). The first subscribe must quiet-revalidate; otherwise mount never fetches and
46+
* seeded rows stay indefinitely stale with `lastAttemptOk: true`.
47+
*/
48+
seedNeedsRevalidate: boolean;
4349
};
4450

4551
/**
@@ -80,6 +86,7 @@ function getStore<T>(key: string): Store<T> {
8086
inflightOwner: null,
8187
visibilityListener: null,
8288
generation: 0,
89+
seedNeedsRevalidate: false,
8390
};
8491
stores.set(key, store);
8592
}
@@ -223,6 +230,8 @@ async function runFetch<T>(
223230
try {
224231
const data = await fetcher(controller.signal);
225232
if (gen !== store.generation || controller.signal.aborted) return;
233+
// Cleared on settle (not at subscribe) so StrictMode's aborted first mount still revalidates.
234+
store.seedNeedsRevalidate = false;
226235
store.snapshot = {
227236
data,
228237
error: undefined,
@@ -233,6 +242,7 @@ async function runFetch<T>(
233242
};
234243
} catch (error) {
235244
if (gen !== store.generation || controller.signal.aborted) return;
245+
store.seedNeedsRevalidate = false;
236246
store.snapshot = {
237247
...store.snapshot,
238248
// Normalize so a loader that rejects with `undefined` still reads as a failure.
@@ -298,9 +308,12 @@ function subscribeResource<T>(
298308
store.fetcherByListener.set(onStoreChange, fetcher);
299309
store.subscriberCount++;
300310

301-
// Cold start only — keep cached data across transient 0→1 resubscribe gaps.
302-
if (store.subscriberCount === 1 && store.snapshot.data === undefined) {
303-
void runFetch(store, fetcher, { replaceInflight: true, owner: onStoreChange });
311+
// Cold start, or a pre-subscribe seed that still needs a network check. Keep
312+
// cached data across transient 0→1 resubscribe gaps when neither applies.
313+
if (store.subscriberCount === 1) {
314+
if (store.snapshot.data === undefined || store.seedNeedsRevalidate) {
315+
void runFetch(store, fetcher, { replaceInflight: true, owner: onStoreChange });
316+
}
304317
}
305318
recomputePoll(store);
306319

@@ -328,7 +341,7 @@ function subscribeResource<T>(
328341
}
329342

330343
/** Module-level fetch cache with useSyncExternalStore subscriptions (no fetch in useEffect). */
331-
export interface ClientResourceOptions {
344+
export interface ClientResourceOptions<T = unknown> {
332345
pollMs?: number;
333346
enabled?: boolean;
334347
/**
@@ -337,18 +350,34 @@ export interface ClientResourceOptions {
337350
* happening off-screen — a restarting server, for instance.
338351
*/
339352
pauseWhenHidden?: boolean;
353+
/**
354+
* Optional seed applied once before the first subscribe (session-cache revisit).
355+
* Lives in the store — not a render-time ref — so React Compiler / react-hooks/refs
356+
* stay quiet while the mount fetch still quiet-revalidates via `seedNeedsRevalidate`.
357+
*/
358+
initialData?: T;
359+
}
360+
361+
/** Seed an empty, unsubscribed store. No-ops when data already exists or someone is listening. */
362+
function seedClientResourceIfEmpty<T>(key: string, data: T): void {
363+
const store = getStore<T>(key);
364+
if (store.subscriberCount !== 0 || store.snapshot.data !== undefined) return;
365+
setClientResourceData(key, data);
340366
}
341367

342368
export function useClientResource<T>(
343369
key: string,
344370
fetcher: (signal: AbortSignal) => Promise<T>,
345-
options?: ClientResourceOptions,
371+
options?: ClientResourceOptions<T>,
346372
): ResourceSnapshot<T> & { refresh: (opts?: { forceLoading?: boolean }) => void } {
347373
const enabled = options?.enabled !== false;
348374
const pollMs = options?.pollMs;
349375
// Default true: a background tab has nobody reading the paint. Opt out for polls that
350376
// must keep running while hidden, such as waiting for a restarted server to answer.
351377
const pauseWhenHidden = options?.pauseWhenHidden !== false;
378+
if (enabled && options?.initialData !== undefined) {
379+
seedClientResourceIfEmpty(key, options.initialData);
380+
}
352381
const fetcherRef = useRef(fetcher);
353382
// Sync latest fetcher every commit. No dep array on purpose: inline fetchers are
354383
// reallocated every render; listing them would re-subscribe forever.
@@ -411,7 +440,7 @@ export function useKeyedClientResource<T>(
411440
key: string,
412441
deps: readonly unknown[],
413442
load: (signal: AbortSignal) => Promise<T>,
414-
options?: ClientResourceOptions,
443+
options?: ClientResourceOptions<T>,
415444
): ResourceSnapshot<T> & { refresh: (opts?: { forceLoading?: boolean }) => void } {
416445
const resource = useClientResource(key, load, options);
417446
const prevDepsRef = useRef<readonly unknown[] | null>(null);
@@ -447,6 +476,9 @@ export function setClientResourceData<T>(key: string, data: T) {
447476
hasSucceeded: true,
448477
lastAttemptOk: true,
449478
};
479+
// Only pre-subscribe seeds need a follow-up fetch. Live publishers (mutation
480+
// results) already hold the fresh value and must not schedule a redundant GET.
481+
store.seedNeedsRevalidate = store.subscriberCount === 0;
450482
emit(store);
451483
}
452484

gui/src/components/CodexAccountPool.tsx

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ import CodexPoolStrategySetting from "./CodexPoolStrategySetting";
1111
import { useCodexAutoSwitch } from "../hooks/useCodexAutoSwitch";
1212
import { readJsonIfOk } from "../fetch-json";
1313
import { CodexAccountPoolCards, CodexAccountPoolReauthBanner } from "./codex-account-pool-cards";
14-
import { DataSurfaceStatus } from "./data-surface";
1514
import { CodexAccountSwitchModal } from "./codex-account-switch-modal";
1615
import { CodexAccountResetModal } from "./codex-account-reset-modal";
1716
import { CodexAccountPoolLoadStates, CodexAccountPoolMainCard, CodexAccountPoolPageHead } from "./codex-account-pool-main-card";
@@ -57,10 +56,6 @@ export default function CodexAccountPool({ apiBase, accountModeState = null, ban
5756
const ownController = useCodexAccountPool(apiBase, !injectedController);
5857
const controller = injectedController ?? ownController;
5958
const { accounts, activeId, loadState, switchingId, pauseUpdatingId, pausingExhausted, load } = controller;
60-
// The controller owns the visible progress signal. A forced quota refresh keeps rows on screen
61-
// and can take ~1s (longer when the server has to refill per-account quota), so without this the
62-
// wait is invisible; `refreshingQuota` below stays local because it only guards its own button.
63-
const { refreshing } = controller;
6459
const [confirm, setConfirm] = useState<CodexAccountEntry | null>(null);
6560
const [showAdd, setShowAdd] = useState(false);
6661
const [reauthId, setReauthId] = useState<string | null>(null);
@@ -277,12 +272,6 @@ export default function CodexAccountPool({ apiBase, accountModeState = null, ban
277272
onRetry={() => { void load(); }}
278273
/>
279274

280-
{/* Revalidation over existing rows. The cold branch above already owns a live region, so
281-
this only renders once rows are on screen, keeping one announcement per transition. */}
282-
{refreshing && accounts.length > 0 && (
283-
<DataSurfaceStatus live={loadState !== "error"}>{t("common.loading")}</DataSurfaceStatus>
284-
)}
285-
286275
{!(loadState === "loading" && accounts.length === 0) && (
287276
<>
288277
<CodexAccountPoolMainCard

gui/src/components/apikeys-workspace/ApiKeysWorkspace.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ export interface ApiKeysWorkspaceProps {
3939
copied: boolean;
4040
filteredModels: ExternalModelRow[];
4141
modelsLoading: boolean;
42+
/** Quiet revalidation / retry over rows already on screen — not a skeleton. */
43+
modelsRefreshing?: boolean;
4244
modelsLoadFailed: boolean;
4345
modelCount: number;
4446
hasModelData: boolean;
@@ -76,6 +78,7 @@ export default function ApiKeysWorkspace({
7678
copied,
7779
filteredModels,
7880
modelsLoading,
81+
modelsRefreshing = false,
7982
modelsLoadFailed,
8083
modelCount,
8184
hasModelData,
@@ -420,6 +423,7 @@ export default function ApiKeysWorkspace({
420423
<ApiKeysModelsPanel
421424
filteredModels={filteredModels}
422425
modelsLoading={modelsLoading}
426+
modelsRefreshing={modelsRefreshing}
423427
modelsLoadFailed={modelsLoadFailed}
424428
modelCount={modelCount}
425429
hasModelData={hasModelData}

gui/src/components/section-tabs.tsx

Lines changed: 51 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@
88
* The active tab follows the scroll position, so the strip reports where you are rather
99
* than only where you last clicked.
1010
*/
11-
import { useEffect, useRef, useState } from "react";
12-
import { sectionAnchorId, sectionAnchorPrefix } from "../section-anchors";
11+
import { useCallback, useEffect, useRef, useState } from "react";
12+
import { SECTION_TAB_SCROLL_LOCK_MS, sectionAnchorId, sectionAnchorPrefix } from "../section-anchors";
1313

1414
export interface SectionTabItem {
1515
id: string;
@@ -28,7 +28,41 @@ export function SectionTabs({
2828
ariaLabel: string;
2929
}) {
3030
const [active, setActive] = useState(items[0]?.id ?? "");
31-
const stripRef = useRef<HTMLDivElement | null>(null);
31+
/** While set, scroll-spy ignores intermediate sections during smooth scroll-to-click. */
32+
const scrollLockRef = useRef<string | null>(null);
33+
const scrollLockTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
34+
35+
const clearScrollLock = useCallback(() => {
36+
scrollLockRef.current = null;
37+
if (scrollLockTimerRef.current !== null) {
38+
clearTimeout(scrollLockTimerRef.current);
39+
scrollLockTimerRef.current = null;
40+
}
41+
}, []);
42+
43+
/** Timeout path: drop the click lock and re-read the visible section. */
44+
const expireScrollLock = useCallback(() => {
45+
clearScrollLock();
46+
// If the user interrupted smooth scroll, the target may never cross the observer threshold —
47+
// without a resync `active` would stay on the clicked tab while another section is on screen.
48+
// Prefer the heading nearest the sticky-strip reading line (not only those with top <= 120),
49+
// so a destination that stopped mid-viewport still wins over an off-screen prior heading.
50+
let bestId: string | null = null;
51+
let bestDistance = Number.POSITIVE_INFINITY;
52+
const readingLine = 72;
53+
for (const item of items) {
54+
const node = document.getElementById(sectionAnchorId(scope, item.id));
55+
if (!node) continue;
56+
const distance = Math.abs(node.getBoundingClientRect().top - readingLine);
57+
if (distance < bestDistance) {
58+
bestDistance = distance;
59+
bestId = item.id;
60+
}
61+
}
62+
if (bestId) setActive(bestId);
63+
}, [clearScrollLock, items, scope]);
64+
65+
useEffect(() => () => clearScrollLock(), [clearScrollLock]);
3266

3367
// Follow the scroll position. `rootMargin` biases the observer toward the top of the
3468
// viewport so the heading you are reading wins, not whatever is technically centred.
@@ -41,6 +75,16 @@ export function SectionTabs({
4175

4276
const observer = new IntersectionObserver(
4377
entries => {
78+
const locked = scrollLockRef.current;
79+
if (locked) {
80+
const lockedNode = document.getElementById(sectionAnchorId(scope, locked));
81+
const lockedVisible = entries.some(entry => entry.isIntersecting && entry.target === lockedNode);
82+
if (lockedVisible) {
83+
clearScrollLock();
84+
setActive(locked);
85+
}
86+
return;
87+
}
4488
const visible = entries
4589
.filter(entry => entry.isIntersecting)
4690
.sort((a, b) => a.boundingClientRect.top - b.boundingClientRect.top)[0];
@@ -52,19 +96,21 @@ export function SectionTabs({
5296
);
5397
for (const node of nodes) observer.observe(node);
5498
return () => observer.disconnect();
55-
}, [items, scope]);
99+
}, [clearScrollLock, items, scope]);
56100

57101
const go = (id: string) => {
58102
const target = document.getElementById(sectionAnchorId(scope, id));
59103
if (!target) return;
104+
scrollLockRef.current = id;
105+
if (scrollLockTimerRef.current !== null) clearTimeout(scrollLockTimerRef.current);
106+
scrollLockTimerRef.current = setTimeout(expireScrollLock, SECTION_TAB_SCROLL_LOCK_MS);
60107
setActive(id);
61108
// `scroll-margin-top` on the target keeps the heading clear of the pinned strip.
62109
target.scrollIntoView({ behavior: "smooth", block: "start" });
63110
};
64111

65112
return (
66113
<div
67-
ref={stripRef}
68114
className="page-tabs section-tabs"
69115
role="tablist"
70116
aria-label={ariaLabel}

gui/src/components/storage-workspace/StorageWorkspace.tsx

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -169,18 +169,18 @@ export default function StorageWorkspace({ report, locale }: StorageWorkspacePro
169169
</div>
170170
) : (
171171
<div className="stw-overview">
172-
<div className="usage-cards">
173-
<div className="stat">
174-
<div className="muted">{t("storage.card.total")}</div>
175-
<div className="stat-value">{formatBytes(report.total.bytes, locale)}</div>
172+
<div className="stw-summary">
173+
<div className="stw-summary-card">
174+
<div className="stw-summary-label">{t("storage.card.total")}</div>
175+
<div className="stw-summary-value">{formatBytes(report.total.bytes, locale)}</div>
176176
</div>
177-
<div className="stat">
178-
<div className="muted">{t("storage.card.files")}</div>
179-
<div className="stat-value">{report.total.fileCount.toLocaleString(locale)}</div>
177+
<div className="stw-summary-card">
178+
<div className="stw-summary-label">{t("storage.card.files")}</div>
179+
<div className="stw-summary-value">{report.total.fileCount.toLocaleString(locale)}</div>
180180
</div>
181-
<div className="stat">
182-
<div className="muted">{t("storage.card.home")}</div>
183-
<div className="stat-value mono stw-home-path" title={report.codexHome}>{report.codexHome}</div>
181+
<div className="stw-summary-card">
182+
<div className="stw-summary-label">{t("storage.card.home")}</div>
183+
<div className="stw-summary-value mono stw-home-path" title={report.codexHome}>{report.codexHome}</div>
184184
</div>
185185
</div>
186186

gui/src/data-surface.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@ export type DataSurfaceOptions<T> = {
4343
enabled?: boolean;
4444
/** Forwarded to the resource layer; see ClientResourceOptions.pauseWhenHidden. */
4545
pauseWhenHidden?: boolean;
46+
/** Forwarded to the resource layer; see ClientResourceOptions.initialData. */
47+
initialData?: T;
4648
};
4749

4850
/**

0 commit comments

Comments
 (0)