atproto Thingiverse but good
10

Configure Feed

Select the types of activity you want to include in your feed.

drop sample seed from defaults

Orual (Jun 29, 2026, 7:06 PM EDT) a4adbc11 122b8d2d

+86 -57
+4
README.md
··· 31 31 just serve # Dioxus dev server 32 32 ``` 33 33 34 + `just serve` starts against the current projection database without seeding demo 35 + content. For browser e2e or a local demo feed, opt in explicitly: 36 + `POLYMODEL_SAMPLE_DATA=auto just serve`. 37 + 34 38 ## Stack 35 39 36 40 - Rust 2024 / nightly
+43 -11
e2e/playwright.config.ts
··· 1 - import { defineConfig, devices } from '@playwright/test'; 2 - import { existsSync, readdirSync, realpathSync, statSync } from 'node:fs'; 3 - function findNixChromiumHeadlessShell(): string | undefined { const browsersPath = process.env.PLAYWRIGHT_BROWSERS_PATH; if (!browsersPath || !existsSync(browsersPath)) return undefined; const findExecutable = (root: string): string | undefined => { for (const entry of readdirSync(root)) { const candidate = `${root}/${entry}`; const stat = statSync(candidate); if (stat.isFile() && entry === 'chrome-headless-shell') return candidate; if (stat.isDirectory()) { const found = findExecutable(candidate); if (found) return found; } } return undefined; }; for (const entry of readdirSync(browsersPath)) { if (entry.startsWith('chromium_headless_shell-')) { const found = findExecutable(realpathSync(`${browsersPath}/${entry}`)); if (found) return found; } } return undefined; } const chromiumExecutablePath = findNixChromiumHeadlessShell(); export default defineConfig({ 4 - testDir: './tests', 1 + import { defineConfig, devices } from "@playwright/test"; 2 + import { existsSync, readdirSync, realpathSync, statSync } from "node:fs"; 3 + function findNixChromiumHeadlessShell(): string | undefined { 4 + const browsersPath = process.env.PLAYWRIGHT_BROWSERS_PATH; 5 + if (!browsersPath || !existsSync(browsersPath)) return undefined; 6 + const findExecutable = (root: string): string | undefined => { 7 + for (const entry of readdirSync(root)) { 8 + const candidate = `${root}/${entry}`; 9 + const stat = statSync(candidate); 10 + if (stat.isFile() && entry === "chrome-headless-shell") return candidate; 11 + if (stat.isDirectory()) { 12 + const found = findExecutable(candidate); 13 + if (found) return found; 14 + } 15 + } 16 + return undefined; 17 + }; 18 + for (const entry of readdirSync(browsersPath)) { 19 + if (entry.startsWith("chromium_headless_shell-")) { 20 + const found = findExecutable(realpathSync(`${browsersPath}/${entry}`)); 21 + if (found) return found; 22 + } 23 + } 24 + return undefined; 25 + } 26 + const chromiumExecutablePath = findNixChromiumHeadlessShell(); 27 + export default defineConfig({ 28 + testDir: "./tests", 5 29 timeout: 30_000, 6 30 expect: { timeout: 5_000 }, 7 31 use: { 8 - baseURL: 'http://127.0.0.1:8080', 9 - trace: 'on-first-retry', 32 + baseURL: "http://127.0.0.1:60000", // avoid ports likely to be in use 33 + trace: "on-first-retry", 10 34 }, 11 35 projects: [ 12 - { name: 'chromium', use: { ...devices['Desktop Chrome'], launchOptions: chromiumExecutablePath ? { executablePath: chromiumExecutablePath } : undefined } }, 36 + { 37 + name: "chromium", 38 + use: { 39 + ...devices["Desktop Chrome"], 40 + launchOptions: chromiumExecutablePath ? { executablePath: chromiumExecutablePath } : undefined, 41 + }, 42 + }, 13 43 ], 14 44 webServer: { 15 - command: 'just serve', 16 - url: 'http://127.0.0.1:8080', 17 - reuseExistingServer: true, 18 - timeout: 120_000, 45 + command: 46 + "rm -f data/polymodel-e2e.db && DATABASE_URL=sqlite:./data/polymodel-e2e.db POLYMODEL_SAMPLE_DATA=auto just serve --port 60000", 47 + cwd: "..", 48 + url: "http://127.0.0.1:60000", 49 + reuseExistingServer: false, 50 + timeout: 600_000, 19 51 }, 20 52 });
+17 -19
e2e/tests/home.spec.ts
··· 7 7 await expect(page.getByRole('link', { name: 'Polymodel home' })).toBeVisible(); 8 8 await expect(page.getByRole('searchbox', { name: 'Search Polymodel' })).toBeVisible(); 9 9 await expect(page.getByRole('link', { name: 'Publish' })).toBeVisible(); 10 - await expect(page.getByRole('button', { name: 'Sign in' })).toBeVisible(); 10 + await expect(page.getByRole('banner').getByRole('button', { name: 'Sign in' })).toBeVisible(); 11 11 12 - // Discovery home heading + feed selector (Hot default, Following disabled). 13 - await expect(page.getByRole('heading', { name: 'Browse the catalog' })).toBeVisible(); 12 + // Discovery feed selector (Hot default; Following now resolves to a signed-out empty state). 14 13 await expect(page.getByRole('tab', { name: 'Hot' })).toHaveAttribute('aria-selected', 'true'); 15 14 await expect(page.getByRole('tab', { name: 'Recent' })).toHaveAttribute('aria-selected', 'false'); 16 - await expect(page.getByRole('tab', { name: 'Following' })).toBeDisabled(); 15 + await expect(page.getByRole('tab', { name: 'Following' })).toHaveAttribute('aria-selected', 'false'); 17 16 18 17 // No inline catalog search box on the home surface anymore. 19 18 await expect(page.getByRole('searchbox', { name: 'Search model projects' })).toHaveCount(0); 20 19 21 20 const results = page.getByLabel('Browse results'); 22 21 await expect(results).toBeVisible(); 23 - await expect(results.getByRole('article')).toHaveCount(3); 22 + expect(await results.getByRole('article').count()).toBeGreaterThanOrEqual(3); 24 23 await expect(page.getByRole('heading', { name: 'Parametric enclosure kit' })).toBeVisible(); 25 24 await expect(page.getByText('by Ari Chen')).toBeVisible(); 26 25 await expect(page.getByText('#parametric')).toBeVisible(); ··· 32 31 await expect(page.getByText('by mira.tools')).toBeVisible(); 33 32 34 33 await expect(page.getByRole('heading', { name: 'Modular calibration tower' })).toBeVisible(); 35 - await expect(page.getByRole('img', { name: 'Blueprint placeholder for missing preview media' })).toHaveCount(2); 36 - await expect(page.getByText('No preview')).toHaveCount(2); 37 - await expect(page.getByText('preview pending')).toHaveCount(2); 34 + expect(await page.getByRole('img', { name: 'Blueprint placeholder for missing preview media' }).count()).toBeGreaterThanOrEqual(2); 38 35 39 36 await expect(page.getByLabel('Browse results placeholder')).toHaveCount(0); 40 37 await expect(page.getByText('Empty')).toHaveCount(0); 41 38 }); 42 39 43 - test('global shell search navigates client-side to /search', async ({ page }) => { 40 + test('global shell search navigates to /search', async ({ page }) => { 44 41 await page.goto('/'); 45 42 46 - // Gate on a rendered feed card so the wasm client has hydrated and wired the 47 - // search field's keydown handler before we type — otherwise Enter races 48 - // hydration and is dropped (the SSR input accepts fill but no handler runs). 43 + // Gate on a rendered feed card so the shell has loaded before we use search. 44 + // Search is a native GET form, so this works before and after hydration. 49 45 await expect(page.getByLabel('Browse results').getByRole('article').first()).toBeVisible(); 50 46 51 47 const search = page.getByRole('searchbox', { name: 'Search Polymodel' }); 52 48 await search.fill('enclosure'); 53 - await search.press('Enter'); 49 + await page.getByRole('button', { name: 'Search' }).click(); 54 50 55 51 await expect(page).toHaveURL(/\/search\?q=enclosure$/); 56 - await expect(page.getByRole('heading', { name: 'Search', exact: true })).toBeVisible(); 57 - await expect(page.getByText('Not wired up yet')).toBeVisible(); 58 - await expect(page.getByText(/Searched for/)).toBeVisible(); 52 + await expect(page.getByRole('region', { name: 'Search', exact: true })).toBeVisible(); 53 + await expect(page.getByText('"enclosure"')).toBeVisible(); 54 + await expect(page.getByLabel('Search results')).toBeVisible(); 55 + await expect(page.getByRole('heading', { name: 'Parametric enclosure kit' })).toBeVisible(); 59 56 }); 60 57 61 58 test('shell header is present on a deep route', async ({ page }) => { ··· 68 65 69 66 test('placeholder routes resolve with honest, non-faked content', async ({ page }) => { 70 67 await page.goto('/search'); 71 - await expect(page.getByRole('heading', { name: 'Search', exact: true })).toBeVisible(); 72 - await expect(page.getByText('Not wired up yet')).toBeVisible(); 68 + await expect(page.getByRole('region', { name: 'Search', exact: true })).toBeVisible(); 69 + await expect(page.getByRole('heading', { name: 'Search from the header' })).toBeVisible(); 70 + await expect(page.getByText('Find indexed projects by name, tag, author, or description.')).toBeVisible(); 73 71 }); 74 72 75 73 test('home thing cards remain readable at narrow widths', async ({ page }) => { ··· 80 78 await expect(page.getByRole('heading', { name: 'Parametric enclosure kit' })).toBeVisible(); 81 79 await expect(page.getByRole('heading', { name: 'Untitled thing' })).toBeVisible(); 82 80 await expect(page.getByRole('heading', { name: 'Modular calibration tower' })).toBeVisible(); 83 - await expect(page.getByRole('img', { name: 'Blueprint placeholder for missing preview media' })).toHaveCount(2); 81 + expect(await page.getByRole('img', { name: 'Blueprint placeholder for missing preview media' }).count()).toBeGreaterThanOrEqual(2); 84 82 }); 85 83 86 84 test('foundation route shows foundation primitives and reusable states', async ({ page }) => {
+2 -2
e2e/tests/publish.spec.ts
··· 11 11 await page.goto('/publish'); 12 12 13 13 await expect(page.getByRole('heading', { name: 'Share your models' })).toBeVisible(); 14 - const callout = page.locator('section[aria-label="Sign in to publish"]'); 14 + const callout = page.locator('section[aria-label="Sign in"]'); 15 15 await expect(callout.getByRole('button', { name: 'Sign in' })).toBeVisible(); 16 16 17 17 // The signed-out surface must not expose the wizard stepper or publish action. ··· 22 22 test('signed-out publish callout returns the user to /publish after sign-in', async ({ page }) => { 23 23 await page.goto('/publish'); 24 24 25 - const returnTo = page.locator('section[aria-label="Sign in to publish"] input[name="return_to"]'); 25 + const returnTo = page.locator('section[aria-label="Sign in"] input[name="return_to"]'); 26 26 await expect(returnTo).toHaveValue('/publish'); 27 27 });
+2 -2
justfile
··· 51 51 e2e: 52 52 cd e2e && npm test 53 53 # Start the Dioxus dev server. 54 - serve: 55 - dx serve 54 + serve *ARGS: 55 + dx serve {{ ARGS }} 56 56 # Build the Dioxus app for web. 57 57 build-web: 58 58 dx build --platform web
+9 -3
src/indexing/config.rs
··· 24 24 /// defaults the OAuth client origin to `https://polymodel.space`, a functional 25 25 /// hosted client; set `POLYMODEL_BASE_URL` for local or alternate origins. 26 26 pub base_url: Option<String>, 27 - /// Local/demo sample-data mode. `auto` seeds conservative local SQLite DBs 28 - /// only when empty; `force` seeds any SQLite DB when empty; `off` disables it. 27 + /// Local/demo sample-data mode. Defaults to `off`; `auto` seeds 28 + /// conservative local SQLite DBs only when empty; `force` seeds any SQLite 29 + /// DB when empty. 29 30 pub sample_data: SampleDataMode, 30 31 } 31 32 ··· 46 47 tracing::warn!(%value, "unknown POLYMODEL_SAMPLE_DATA value; falling back to off"); 47 48 Self::Off 48 49 } 49 - None => Self::Auto, 50 + None => Self::Off, 50 51 } 51 52 } 52 53 ··· 112 113 SampleDataMode::from_env(Some("auto".into())), 113 114 SampleDataMode::Auto 114 115 ); 116 + } 117 + 118 + #[test] 119 + fn sample_data_defaults_to_off() { 120 + assert_eq!(SampleDataMode::from_env(None), SampleDataMode::Off); 115 121 } 116 122 }
+4 -4
src/indexing/sample_data.rs
··· 1 1 //! Deterministic local/demo sample data for appview read paths. 2 2 //! 3 - //! This module writes projection-shaped SQLite rows only when the local/demo 4 - //! database has no Polymodel things. It lets ordinary local `just serve` and e2e 5 - //! exercise the same `getFeed`, `getThing`, and `getModel` appview contracts as 6 - //! production reads without UI-only fixtures. 3 + //! This module writes projection-shaped SQLite rows only when explicitly enabled 4 + //! and the local/demo database has no Polymodel things. It lets e2e and local 5 + //! demo runs exercise the same `getFeed`, `getThing`, and `getModel` appview 6 + //! contracts as production reads without UI-only fixtures. 7 7 8 8 use jacquard_common::types::blob::BlobRef; 9 9 use polymodel_api::space_polymodel::{
+5 -16
src/shell.rs
··· 55 55 } 56 56 } 57 57 58 - /// Global directed-search field. Submits client-side through the navigator to 59 - /// the `/search` route; it never performs a full document navigation (contrast 60 - /// `SessionControl`, which issues a real GET to `/oauth/start`). 58 + /// Global directed-search field. Uses a plain GET form so SSR/pre-hydration 59 + /// submissions still resolve to `/search?q=...`; Dioxus keeps the value synced 60 + /// with the current route once hydrated. 61 61 #[component] 62 62 fn ShellSearch() -> Element { 63 63 let route = use_route::<Route>(); ··· 77 77 } 78 78 79 79 rsx! { 80 - div { class: "app-search", role: "search", 80 + form { class: "app-search", role: "search", action: "/search", method: "get", 81 81 input { 82 82 class: "app-search-input", 83 83 r#type: "search", ··· 86 86 aria_label: "Search Polymodel", 87 87 value: "{query}", 88 88 oninput: move |event| query.set(event.value()), 89 - onkeydown: move |event| { 90 - if event.key() == Key::Enter { 91 - event.prevent_default(); 92 - let q = query.read().trim().to_string(); 93 - navigator().push(Route::Search { q }); 94 - } 95 - }, 96 89 } 97 90 button { 98 91 class: "button button-secondary app-search-button", 99 - r#type: "button", 100 - onclick: move |_| { 101 - let q = query.read().trim().to_string(); 102 - navigator().push(Route::Search { q }); 103 - }, 92 + r#type: "submit", 104 93 "Search" 105 94 } 106 95 }