deepspace 0.4.1 → 0.4.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +446 -178
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +188 -1
- package/dist/index.js +577 -103
- package/dist/index.js.map +1 -1
- package/dist/testing.d.ts +115 -0
- package/features/file-attachments/feature.json +1 -0
- package/features/file-attachments/src/useFilePreview.ts +1 -1
- package/features/integration-test/src/IntegrationTestPage.tsx +1 -0
- package/features/integration-test/src/integration.spec.ts +14 -5
- package/features/messaging/src/components/chat/MessageList.tsx +1 -1
- package/features/presence-test/src/PresenceTestPage.tsx +1 -1
- package/features/search-bar/feature.json +34 -0
- package/features/search-bar/src/SearchOverlay.tsx +710 -0
- package/features/testing/src/subrequest-burn-action.ts +6 -3
- package/package.json +1 -1
- package/scripts/add-feature.cjs +5 -4
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import * as _playwright_test from '@playwright/test';
|
|
2
|
+
import { BrowserContext, Page, Browser } from '@playwright/test';
|
|
3
|
+
export { expect } from '@playwright/test';
|
|
4
|
+
|
|
5
|
+
interface MultiplayerUser {
|
|
6
|
+
context: BrowserContext;
|
|
7
|
+
page: Page;
|
|
8
|
+
email: string;
|
|
9
|
+
name: string;
|
|
10
|
+
/** Test account user ID, if known from the accounts registry. */
|
|
11
|
+
userId?: string;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Fixture function signature. Pass either a count (`users(2)`) or an
|
|
15
|
+
* array of account names (`users(['Alice', 'Bob'])`). Returns one entry
|
|
16
|
+
* per requested user.
|
|
17
|
+
*/
|
|
18
|
+
type UsersFixture = (selector: number | string[], options?: {
|
|
19
|
+
label?: string;
|
|
20
|
+
}) => Promise<MultiplayerUser[]>;
|
|
21
|
+
interface MultiplayerFixtures {
|
|
22
|
+
users: UsersFixture;
|
|
23
|
+
}
|
|
24
|
+
declare const test: _playwright_test.TestType<_playwright_test.PlaywrightTestArgs & _playwright_test.PlaywrightTestOptions & MultiplayerFixtures, _playwright_test.PlaywrightWorkerArgs & _playwright_test.PlaywrightWorkerOptions>;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Account-credential plumbing for the testing module.
|
|
28
|
+
*
|
|
29
|
+
* Reads test accounts from `~/.deepspace/test-accounts.json` (the same
|
|
30
|
+
* file `deepspace test-accounts create` writes). Each account has at
|
|
31
|
+
* minimum `{email, password}`; `name` and `label` are optional.
|
|
32
|
+
*
|
|
33
|
+
* Used by the multi-user Playwright fixture in `./fixtures.ts` and is
|
|
34
|
+
* also exported standalone in case suites want to do their own
|
|
35
|
+
* orchestration.
|
|
36
|
+
*/
|
|
37
|
+
interface TestAccount {
|
|
38
|
+
email: string;
|
|
39
|
+
password: string;
|
|
40
|
+
name?: string;
|
|
41
|
+
label?: string | null;
|
|
42
|
+
id?: string;
|
|
43
|
+
userId?: string;
|
|
44
|
+
createdAt?: number;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Load all test accounts the developer has created. Returns an empty
|
|
48
|
+
* array if the file doesn't exist yet.
|
|
49
|
+
*/
|
|
50
|
+
declare function loadAllTestAccounts(): TestAccount[];
|
|
51
|
+
/**
|
|
52
|
+
* Pick `count` test accounts from the local registry, optionally
|
|
53
|
+
* filtered by `label`. Order-stable: sorted by `createdAt` ascending so
|
|
54
|
+
* the first test account you created is always the first one returned.
|
|
55
|
+
*
|
|
56
|
+
* Throws with a helpful message if not enough accounts exist.
|
|
57
|
+
*/
|
|
58
|
+
declare function pickTestAccounts(count: number, options?: {
|
|
59
|
+
label?: string;
|
|
60
|
+
}): TestAccount[];
|
|
61
|
+
/**
|
|
62
|
+
* Find a single account by name. Used by `users(['Alice', 'Bob'])`.
|
|
63
|
+
* Throws if not found.
|
|
64
|
+
*/
|
|
65
|
+
declare function findTestAccountByName(name: string): TestAccount;
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Per-account `storageState` cache for Playwright.
|
|
69
|
+
*
|
|
70
|
+
* Better Auth applies a per-IP rate limit on `/api/auth/sign-in/email`
|
|
71
|
+
* (~5 sign-ins / 60s / endpoint). A multiplayer suite that spins up
|
|
72
|
+
* 5+ users per spec would hit the limit and start failing in non-obvious
|
|
73
|
+
* ways. This module signs each account in *once*, persists the
|
|
74
|
+
* resulting browser cookies + storage to disk, and reuses the file on
|
|
75
|
+
* subsequent runs.
|
|
76
|
+
*
|
|
77
|
+
* Cache layout:
|
|
78
|
+
* ~/.deepspace/playwright-states/<sha256(email)>.json
|
|
79
|
+
*
|
|
80
|
+
* Validity: a cached file is reused if Playwright successfully loads it
|
|
81
|
+
* AND the resulting context produces a non-anonymous session on the
|
|
82
|
+
* target app. If validation fails, we fall back to a fresh sign-in and
|
|
83
|
+
* overwrite the cache.
|
|
84
|
+
*/
|
|
85
|
+
|
|
86
|
+
interface EnsureStorageStateOptions {
|
|
87
|
+
/** Max age of a cached state file before we re-sign-in. Default 7 days. */
|
|
88
|
+
maxAgeMs?: number;
|
|
89
|
+
/** Force a fresh sign-in even if the cache is fresh. */
|
|
90
|
+
force?: boolean;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Ensure a Playwright `storageState` file exists for `account` and
|
|
94
|
+
* return its path. Signs in once if the cache is missing, stale, or
|
|
95
|
+
* `force: true`.
|
|
96
|
+
*/
|
|
97
|
+
declare function ensureStorageState(browser: Browser, account: {
|
|
98
|
+
email: string;
|
|
99
|
+
password: string;
|
|
100
|
+
}, baseURL: string, options?: EnsureStorageStateOptions): Promise<string>;
|
|
101
|
+
/**
|
|
102
|
+
* Convenience: open a fresh `BrowserContext` for `account` using the
|
|
103
|
+
* cached storage state (signing in if needed). The caller is responsible
|
|
104
|
+
* for closing the context.
|
|
105
|
+
*/
|
|
106
|
+
declare function newSignedInContext(browser: Browser, account: {
|
|
107
|
+
email: string;
|
|
108
|
+
password: string;
|
|
109
|
+
}, baseURL: string, options?: EnsureStorageStateOptions): Promise<BrowserContext>;
|
|
110
|
+
/** Read a cached storageState file (mostly for debugging). */
|
|
111
|
+
declare function readCachedState(email: string): unknown | null;
|
|
112
|
+
/** Where the cache for `email` lives. Useful for diagnostics. */
|
|
113
|
+
declare function getStatePathForEmail(email: string): string;
|
|
114
|
+
|
|
115
|
+
export { type EnsureStorageStateOptions, type MultiplayerUser, type TestAccount, type UsersFixture, ensureStorageState, findTestAccountByName, getStatePathForEmail, loadAllTestAccounts, newSignedInContext, pickTestAccounts, readCachedState, test };
|
|
@@ -413,7 +413,7 @@ async function loadPdfJs(): Promise<typeof import('pdfjs-dist')> {
|
|
|
413
413
|
pdfjsLoadPromise = (async () => {
|
|
414
414
|
const [pdfjsLib, workerModule] = await Promise.all([
|
|
415
415
|
import('pdfjs-dist'),
|
|
416
|
-
//
|
|
416
|
+
// Vite's `?worker` suffix yields a Worker constructor (typed via vite/client).
|
|
417
417
|
import('pdfjs-dist/build/pdf.worker.min.mjs?worker'),
|
|
418
418
|
])
|
|
419
419
|
const PdfWorker = (workerModule as { default: new () => Worker }).default
|
|
@@ -1,16 +1,25 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Integration proxy E2E — drives the /integration-test page to exercise
|
|
3
|
+
* the authenticated billing + proxy flow end to end.
|
|
4
|
+
*
|
|
5
|
+
* Public email/password sign-up is disabled, so the authenticated test
|
|
6
|
+
* signs in as a pre-created test account via the `users` fixture (from
|
|
7
|
+
* deepspace/testing), which handles sign-in caching, context creation,
|
|
8
|
+
* and cleanup. Create at least one test account first:
|
|
9
|
+
* npx deepspace test-accounts create --email integ@deepspace.test --password TestPass123! --name "Integration Tester"
|
|
10
|
+
*/
|
|
11
|
+
import { test, expect } from 'deepspace/testing'
|
|
3
12
|
|
|
4
13
|
async function waitForApp(page: import('@playwright/test').Page) {
|
|
5
14
|
await page.waitForSelector('[data-testid="app-navigation"]', { timeout: 15000 })
|
|
6
15
|
}
|
|
7
16
|
|
|
8
17
|
test.describe('Integration API', () => {
|
|
9
|
-
test('authenticated user can call OpenAI via integration proxy', async ({
|
|
10
|
-
|
|
11
|
-
await waitForApp(page)
|
|
18
|
+
test('authenticated user can call OpenAI via integration proxy', async ({ users }) => {
|
|
19
|
+
const [{ page }] = await users(1)
|
|
12
20
|
|
|
13
21
|
await page.goto('/integration-test')
|
|
22
|
+
await waitForApp(page)
|
|
14
23
|
await page.waitForSelector('[data-testid="integration-submit"]', { timeout: 10000 })
|
|
15
24
|
|
|
16
25
|
// Default is openai/chat-completion — click submit
|
|
@@ -235,7 +235,7 @@ export function MessageList({ channelId, onStartDM }: MessageListProps) {
|
|
|
235
235
|
if (actionSheet) setEditingMessageId(actionSheet.messageId)
|
|
236
236
|
}}
|
|
237
237
|
onDelete={() => {
|
|
238
|
-
if (actionSheet)
|
|
238
|
+
if (actionSheet) softDelete(actionSheet.messageId)
|
|
239
239
|
}}
|
|
240
240
|
onReply={() => {
|
|
241
241
|
if (actionSheet) setThreadMessageId(actionSheet.messageId)
|
|
@@ -95,7 +95,7 @@ export default function PresenceTestPage() {
|
|
|
95
95
|
</span>
|
|
96
96
|
</div>
|
|
97
97
|
<div className="text-xs text-muted-foreground space-x-2">
|
|
98
|
-
{peer.state.cursor && (
|
|
98
|
+
{!!peer.state.cursor && (
|
|
99
99
|
<span data-testid={`presence-peer-cursor-${peer.userId}`}>
|
|
100
100
|
cursor: {(peer.state.cursor as { x: number; y: number }).x},{(peer.state.cursor as { x: number; y: number }).y}
|
|
101
101
|
</span>
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "search-bar",
|
|
3
|
+
"name": "Search Bar",
|
|
4
|
+
"category": "data",
|
|
5
|
+
"description": "Drop-in InlineSearch/SearchOverlay for app search; use instead of raw SearchInput when showing or filtering results",
|
|
6
|
+
"details": "Use this before hand-rolling search over local arrays, feeds, tables, pickers, or RecordRoom records. Installs a component-only search toolkit with lightweight InlineSearch + useSearchIndex for simple in-page filtering, plus SearchOverlay for global/command search, keyboard navigation, grouped results, loading/error/empty states, click selection, optional Enter-to-submit query navigation, opt-in match highlighting, overlay-only recent result and query searches, and a debounced async-search helper. It does not add a route or demo page; import the installed component into the page you are building.",
|
|
7
|
+
"files": [
|
|
8
|
+
{
|
|
9
|
+
"src": "src/SearchOverlay.tsx",
|
|
10
|
+
"dest": "src/components/search/SearchOverlay.tsx"
|
|
11
|
+
}
|
|
12
|
+
],
|
|
13
|
+
"requires": [],
|
|
14
|
+
"instructions": [
|
|
15
|
+
"Run npx deepspace add search-bar before building any search UI that filters records or displays results; it only installs src/components/search/SearchOverlay.tsx and does not add app routes.",
|
|
16
|
+
"Prefer InlineSearch + useSearchIndex for simple page, feed, table, list, picker, or RecordRoom filtering instead of composing SearchInput manually.",
|
|
17
|
+
"Use the raw SearchInput primitive only when you need a bare input with no result list, ranking, keyboard navigation, empty state, or selection handling.",
|
|
18
|
+
"Use InlineSearch when search belongs to a specific page section and results should appear beneath that input.",
|
|
19
|
+
"Use SearchOverlay when search is temporary, global, command-like, includes recent searches, submits partial text to a search-results page, or should interrupt the current workflow.",
|
|
20
|
+
"Pass onSearchSubmit when pressing Enter should navigate to or render a full search-results page for the typed query; result clicks still call onSelect.",
|
|
21
|
+
"Ask the user which placement they prefer only when both overlay and inline search are plausible and the page context does not make the placement obvious.",
|
|
22
|
+
"Use SearchOverlay's recentStorageKey, maxRecentItems, and showRecentSearches props when recent selections or submitted text queries should appear before the user types; keep recent searches off inline controls unless the surrounding UI has room for them.",
|
|
23
|
+
"Use getHighlightedParts with renderTitle, renderSubtitle, or renderDescription when matching text should be highlighted; leave highlighting to app-specific renderers so colors, emphasis, and density match the local design."
|
|
24
|
+
],
|
|
25
|
+
"patterns": [
|
|
26
|
+
"For local feed/list filtering, keep the source array in the page, derive results with useSearchIndex, map results to SearchItem[], and render InlineSearch where the user expects to search.",
|
|
27
|
+
"For RecordRoom data, call useQuery in the page and pass records mapped to SearchItem[] into InlineSearch or SearchOverlay.",
|
|
28
|
+
"For overlay search that previews matching items but routes Enter to a results page, pass onSearchSubmit={(query) => navigate(`/search?q=${encodeURIComponent(query)}`)} and keep onSelect for direct result clicks.",
|
|
29
|
+
"Keep selections app-specific: create or update records in the onSelect handler only after the user explicitly chooses a result.",
|
|
30
|
+
"Use mocked or debounced async providers during prototyping before wiring live integration calls.",
|
|
31
|
+
"For privacy-sensitive search contexts, set showRecentSearches={false} or use a scoped recentStorageKey per page or account context."
|
|
32
|
+
],
|
|
33
|
+
"example": "import { InlineSearch, useSearchIndex } from './components/search/SearchOverlay'\n\nconst results = useSearchIndex({\n items: posts,\n query,\n getText: (post) => [post.title, post.body, post.authorName],\n})\n\n<InlineSearch\n query={query}\n onQueryChange={setQuery}\n items={results.map((post) => ({\n id: post.id,\n title: post.title,\n subtitle: post.authorName,\n description: post.body,\n }))}\n onSelect={(item) => openPost(item.id)}\n placeholder=\"Search posts...\"\n/>"
|
|
34
|
+
}
|