fix(landing): publish docs with pages build
This commit is contained in:
parent
b192ed4bae
commit
8558acbbc9
6 changed files with 168 additions and 22 deletions
2
.github/workflows/landing.yml
vendored
2
.github/workflows/landing.yml
vendored
|
|
@ -35,7 +35,7 @@ jobs:
|
||||||
NUXT_APP_BASE_URL: /claude_agent_teams_ui/
|
NUXT_APP_BASE_URL: /claude_agent_teams_ui/
|
||||||
NUXT_PUBLIC_SITE_URL: https://777genius.github.io/claude_agent_teams_ui
|
NUXT_PUBLIC_SITE_URL: https://777genius.github.io/claude_agent_teams_ui
|
||||||
NUXT_PUBLIC_GITHUB_REPO: 777genius/claude_agent_teams_ui
|
NUXT_PUBLIC_GITHUB_REPO: 777genius/claude_agent_teams_ui
|
||||||
run: npx nuxt generate
|
run: npm run generate:all
|
||||||
|
|
||||||
- uses: actions/configure-pages@v5
|
- uses: actions/configure-pages@v5
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ const siteUrl = process.env.NUXT_PUBLIC_SITE_URL || "https://777genius.github.io
|
||||||
const githubRepo = process.env.NUXT_PUBLIC_GITHUB_REPO || "777genius/claude_agent_teams_ui";
|
const githubRepo = process.env.NUXT_PUBLIC_GITHUB_REPO || "777genius/claude_agent_teams_ui";
|
||||||
const githubReleasesUrl = `https://github.com/${githubRepo}/releases`;
|
const githubReleasesUrl = `https://github.com/${githubRepo}/releases`;
|
||||||
const baseURL = process.env.NUXT_APP_BASE_URL || "/";
|
const baseURL = process.env.NUXT_APP_BASE_URL || "/";
|
||||||
|
const basePrefixedDocsPath = `${baseURL.replace(/\/?$/, "/")}docs`;
|
||||||
|
|
||||||
export default defineNuxtConfig({
|
export default defineNuxtConfig({
|
||||||
compatibilityDate: "2026-01-19",
|
compatibilityDate: "2026-01-19",
|
||||||
|
|
@ -57,7 +58,9 @@ export default defineNuxtConfig({
|
||||||
prerender: {
|
prerender: {
|
||||||
ignore: [
|
ignore: [
|
||||||
"/docs",
|
"/docs",
|
||||||
"/docs/**"
|
"/docs/**",
|
||||||
|
basePrefixedDocsPath,
|
||||||
|
`${basePrefixedDocsPath}/**`
|
||||||
],
|
],
|
||||||
routes: [
|
routes: [
|
||||||
...generateI18nRoutes(),
|
...generateI18nRoutes(),
|
||||||
|
|
|
||||||
|
|
@ -4,29 +4,55 @@ import { createRoot } from 'react-dom/client';
|
||||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||||
|
|
||||||
const {
|
const {
|
||||||
|
deleteSnapshotIfMatchesMock,
|
||||||
deleteSnapshotMock,
|
deleteSnapshotMock,
|
||||||
emptySnapshotMock,
|
emptySnapshotMock,
|
||||||
loadSnapshotMock,
|
loadSnapshotMock,
|
||||||
migrateLegacyMock,
|
migrateLegacyMock,
|
||||||
saveSnapshotMock,
|
saveSnapshotMock,
|
||||||
} = vi.hoisted(() => ({
|
} = vi.hoisted(() => {
|
||||||
deleteSnapshotMock: vi.fn(),
|
interface MockSnapshot {
|
||||||
emptySnapshotMock: vi.fn((teamName: string) => ({
|
version: number;
|
||||||
version: 1,
|
teamName: string;
|
||||||
teamName,
|
text: string;
|
||||||
text: '',
|
chips: unknown[];
|
||||||
chips: [],
|
attachments: unknown[];
|
||||||
attachments: [],
|
actionMode?: string;
|
||||||
actionMode: 'do',
|
pendingSendId?: string;
|
||||||
updatedAt: Date.now(),
|
updatedAt: number;
|
||||||
})),
|
}
|
||||||
loadSnapshotMock: vi.fn(),
|
|
||||||
migrateLegacyMock: vi.fn(),
|
const deleteSnapshot = vi.fn();
|
||||||
saveSnapshotMock: vi.fn(),
|
const loadSnapshot = vi.fn();
|
||||||
}));
|
|
||||||
|
return {
|
||||||
|
deleteSnapshotIfMatchesMock: vi.fn(
|
||||||
|
async (teamName: string, predicate: (snapshot: MockSnapshot | null) => boolean) => {
|
||||||
|
const snapshot = (await loadSnapshot(teamName)) as MockSnapshot | null;
|
||||||
|
if (predicate(snapshot)) {
|
||||||
|
await deleteSnapshot(teamName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
),
|
||||||
|
deleteSnapshotMock: deleteSnapshot,
|
||||||
|
emptySnapshotMock: vi.fn((teamName: string) => ({
|
||||||
|
version: 1,
|
||||||
|
teamName,
|
||||||
|
text: '',
|
||||||
|
chips: [],
|
||||||
|
attachments: [],
|
||||||
|
actionMode: 'do',
|
||||||
|
updatedAt: Date.now(),
|
||||||
|
})),
|
||||||
|
loadSnapshotMock: loadSnapshot,
|
||||||
|
migrateLegacyMock: vi.fn(),
|
||||||
|
saveSnapshotMock: vi.fn(),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
vi.mock('@renderer/services/composerDraftStorage', () => ({
|
vi.mock('@renderer/services/composerDraftStorage', () => ({
|
||||||
composerDraftStorage: {
|
composerDraftStorage: {
|
||||||
|
deleteSnapshotIfMatches: deleteSnapshotIfMatchesMock,
|
||||||
deleteSnapshot: deleteSnapshotMock,
|
deleteSnapshot: deleteSnapshotMock,
|
||||||
emptySnapshot: emptySnapshotMock,
|
emptySnapshot: emptySnapshotMock,
|
||||||
loadSnapshot: loadSnapshotMock,
|
loadSnapshot: loadSnapshotMock,
|
||||||
|
|
@ -90,6 +116,7 @@ async function renderLoadedHook(): Promise<{
|
||||||
describe('useComposerDraft pending send lifecycle', () => {
|
describe('useComposerDraft pending send lifecycle', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
vi.stubGlobal('IS_REACT_ACT_ENVIRONMENT', true);
|
vi.stubGlobal('IS_REACT_ACT_ENVIRONMENT', true);
|
||||||
|
deleteSnapshotIfMatchesMock.mockClear();
|
||||||
deleteSnapshotMock.mockReset();
|
deleteSnapshotMock.mockReset();
|
||||||
emptySnapshotMock.mockClear();
|
emptySnapshotMock.mockClear();
|
||||||
loadSnapshotMock.mockReset();
|
loadSnapshotMock.mockReset();
|
||||||
|
|
|
||||||
|
|
@ -571,10 +571,10 @@ export function useComposerDraft(teamName: string): UseComposerDraftResult {
|
||||||
(teamNameForDelete: string, submittedContent?: ComposerDraftContent) =>
|
(teamNameForDelete: string, submittedContent?: ComposerDraftContent) =>
|
||||||
enqueuePersist(async () => {
|
enqueuePersist(async () => {
|
||||||
if (submittedContent != null) {
|
if (submittedContent != null) {
|
||||||
const currentSnapshot = await composerDraftStorage.loadSnapshot(teamNameForDelete);
|
await composerDraftStorage.deleteSnapshotIfMatches(teamNameForDelete, (snapshot) =>
|
||||||
if (!snapshotMatchesContent(currentSnapshot, submittedContent)) {
|
snapshotMatchesContent(snapshot, submittedContent)
|
||||||
return;
|
);
|
||||||
}
|
return;
|
||||||
}
|
}
|
||||||
await composerDraftStorage.deleteSnapshot(teamNameForDelete);
|
await composerDraftStorage.deleteSnapshot(teamNameForDelete);
|
||||||
}),
|
}),
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@
|
||||||
* Drafts persist until explicitly cleared (on send or manual action).
|
* Drafts persist until explicitly cleared (on send or manual action).
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { del, get, set } from 'idb-keyval';
|
import { createStore, del, get, promisifyRequest, set } from 'idb-keyval';
|
||||||
|
|
||||||
import type { InlineChip } from '@renderer/types/inlineChip';
|
import type { InlineChip } from '@renderer/types/inlineChip';
|
||||||
import type { AgentActionMode, AttachmentPayload } from '@shared/types';
|
import type { AgentActionMode, AttachmentPayload } from '@shared/types';
|
||||||
|
|
@ -34,6 +34,7 @@ export interface ComposerDraftSnapshot {
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
const KEY_PREFIX = 'composer:';
|
const KEY_PREFIX = 'composer:';
|
||||||
|
const keyvalStore = createStore('keyval-store', 'keyval');
|
||||||
|
|
||||||
function storageKey(teamName: string): string {
|
function storageKey(teamName: string): string {
|
||||||
return `${KEY_PREFIX}${teamName}`;
|
return `${KEY_PREFIX}${teamName}`;
|
||||||
|
|
@ -83,6 +84,43 @@ function markIdbUnavailable(): void {
|
||||||
idbUnavailable = true;
|
idbUnavailable = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function toError(error: unknown, fallbackMessage: string): Error {
|
||||||
|
return error instanceof Error ? error : new Error(fallbackMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
function deleteMatchingSnapshotInStore(
|
||||||
|
store: IDBObjectStore,
|
||||||
|
key: string,
|
||||||
|
predicate: (snapshot: ComposerDraftSnapshot | null) => boolean
|
||||||
|
): Promise<void> {
|
||||||
|
return new Promise<void>((resolve, reject) => {
|
||||||
|
const request = store.get(key);
|
||||||
|
|
||||||
|
request.onerror = () => {
|
||||||
|
reject(toError(request.error, 'Failed to read composer draft snapshot.'));
|
||||||
|
};
|
||||||
|
|
||||||
|
request.onsuccess = () => {
|
||||||
|
try {
|
||||||
|
const raw = request.result as unknown;
|
||||||
|
const snapshot = raw == null ? null : isValidSnapshot(raw) ? raw : null;
|
||||||
|
const shouldDelete = (raw != null && snapshot == null) || predicate(snapshot);
|
||||||
|
|
||||||
|
if (shouldDelete) {
|
||||||
|
store.delete(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
void promisifyRequest(store.transaction).then(
|
||||||
|
() => resolve(),
|
||||||
|
(error) => reject(toError(error, 'Failed to delete composer draft snapshot.'))
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
reject(toError(error, 'Failed to evaluate composer draft snapshot.'));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// Core API
|
// Core API
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
@ -133,6 +171,26 @@ async function deleteSnapshot(teamName: string): Promise<void> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function deleteSnapshotIfMatches(
|
||||||
|
teamName: string,
|
||||||
|
predicate: (snapshot: ComposerDraftSnapshot | null) => boolean
|
||||||
|
): Promise<void> {
|
||||||
|
const key = storageKey(teamName);
|
||||||
|
if (idbUnavailable) {
|
||||||
|
const snapshot = fallbackStore.get(key) ?? null;
|
||||||
|
if (predicate(snapshot)) fallbackStore.delete(key);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await keyvalStore('readwrite', (store) => deleteMatchingSnapshotInStore(store, key, predicate));
|
||||||
|
} catch {
|
||||||
|
markIdbUnavailable();
|
||||||
|
const snapshot = fallbackStore.get(key) ?? null;
|
||||||
|
if (predicate(snapshot)) fallbackStore.delete(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// Legacy migration
|
// Legacy migration
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
@ -267,6 +325,7 @@ export const composerDraftStorage = {
|
||||||
saveSnapshot,
|
saveSnapshot,
|
||||||
loadSnapshot,
|
loadSnapshot,
|
||||||
deleteSnapshot,
|
deleteSnapshot,
|
||||||
|
deleteSnapshotIfMatches,
|
||||||
migrateLegacy,
|
migrateLegacy,
|
||||||
emptySnapshot,
|
emptySnapshot,
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,35 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||||
const store = new Map<string, unknown>();
|
const store = new Map<string, unknown>();
|
||||||
|
|
||||||
vi.mock('idb-keyval', () => ({
|
vi.mock('idb-keyval', () => ({
|
||||||
|
createStore: vi.fn(
|
||||||
|
() =>
|
||||||
|
async <T>(
|
||||||
|
_txMode: IDBTransactionMode,
|
||||||
|
callback: (objectStore: IDBObjectStore) => T | PromiseLike<T>
|
||||||
|
): Promise<T> => {
|
||||||
|
const objectStore = {
|
||||||
|
transaction: {},
|
||||||
|
get: (key: string) => {
|
||||||
|
const request = {
|
||||||
|
result: store.get(key) ?? undefined,
|
||||||
|
error: null,
|
||||||
|
onsuccess: null as (() => void) | null,
|
||||||
|
onerror: null as (() => void) | null,
|
||||||
|
};
|
||||||
|
queueMicrotask(() => {
|
||||||
|
request.onsuccess?.();
|
||||||
|
});
|
||||||
|
return request;
|
||||||
|
},
|
||||||
|
delete: (key: string) => {
|
||||||
|
store.delete(key);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
return callback(objectStore as unknown as IDBObjectStore);
|
||||||
|
}
|
||||||
|
),
|
||||||
|
promisifyRequest: vi.fn(() => Promise.resolve(undefined)),
|
||||||
get: vi.fn((key: string) => Promise.resolve(store.get(key) ?? undefined)),
|
get: vi.fn((key: string) => Promise.resolve(store.get(key) ?? undefined)),
|
||||||
set: vi.fn((key: string, value: unknown) => {
|
set: vi.fn((key: string, value: unknown) => {
|
||||||
store.set(key, value);
|
store.set(key, value);
|
||||||
|
|
@ -91,6 +120,34 @@ describe('composerDraftStorage', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('deleteSnapshotIfMatches', () => {
|
||||||
|
it('should delete a snapshot when the predicate matches', async () => {
|
||||||
|
await composerDraftStorage.saveSnapshot(
|
||||||
|
'team-a',
|
||||||
|
makeSnapshot('team-a', { pendingSendId: 'send-1' })
|
||||||
|
);
|
||||||
|
|
||||||
|
await composerDraftStorage.deleteSnapshotIfMatches(
|
||||||
|
'team-a',
|
||||||
|
(snapshot) => snapshot?.pendingSendId === 'send-1'
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(await composerDraftStorage.loadSnapshot('team-a')).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should keep a snapshot when the predicate does not match', async () => {
|
||||||
|
const snap = makeSnapshot('team-a', { pendingSendId: 'newer-send' });
|
||||||
|
await composerDraftStorage.saveSnapshot('team-a', snap);
|
||||||
|
|
||||||
|
await composerDraftStorage.deleteSnapshotIfMatches(
|
||||||
|
'team-a',
|
||||||
|
(snapshot) => snapshot?.pendingSendId === 'older-send'
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(await composerDraftStorage.loadSnapshot('team-a')).toEqual(snap);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('team isolation', () => {
|
describe('team isolation', () => {
|
||||||
it('should isolate drafts by teamName', async () => {
|
it('should isolate drafts by teamName', async () => {
|
||||||
const snapA = makeSnapshot('team-a', { text: 'from team A' });
|
const snapA = makeSnapshot('team-a', { text: 'from team A' });
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue