···3131just serve # Dioxus dev server
3232```
33333434+`just serve` starts against the current projection database without seeding demo
3535+content. For browser e2e or a local demo feed, opt in explicitly:
3636+`POLYMODEL_SAMPLE_DATA=auto just serve`.
3737+3438## Stack
35393640- Rust 2024 / nightly
···77 await expect(page.getByRole('link', { name: 'Polymodel home' })).toBeVisible();
88 await expect(page.getByRole('searchbox', { name: 'Search Polymodel' })).toBeVisible();
99 await expect(page.getByRole('link', { name: 'Publish' })).toBeVisible();
1010- await expect(page.getByRole('button', { name: 'Sign in' })).toBeVisible();
1010+ await expect(page.getByRole('banner').getByRole('button', { name: 'Sign in' })).toBeVisible();
11111212- // Discovery home heading + feed selector (Hot default, Following disabled).
1313- await expect(page.getByRole('heading', { name: 'Browse the catalog' })).toBeVisible();
1212+ // Discovery feed selector (Hot default; Following now resolves to a signed-out empty state).
1413 await expect(page.getByRole('tab', { name: 'Hot' })).toHaveAttribute('aria-selected', 'true');
1514 await expect(page.getByRole('tab', { name: 'Recent' })).toHaveAttribute('aria-selected', 'false');
1616- await expect(page.getByRole('tab', { name: 'Following' })).toBeDisabled();
1515+ await expect(page.getByRole('tab', { name: 'Following' })).toHaveAttribute('aria-selected', 'false');
17161817 // No inline catalog search box on the home surface anymore.
1918 await expect(page.getByRole('searchbox', { name: 'Search model projects' })).toHaveCount(0);
20192120 const results = page.getByLabel('Browse results');
2221 await expect(results).toBeVisible();
2323- await expect(results.getByRole('article')).toHaveCount(3);
2222+ expect(await results.getByRole('article').count()).toBeGreaterThanOrEqual(3);
2423 await expect(page.getByRole('heading', { name: 'Parametric enclosure kit' })).toBeVisible();
2524 await expect(page.getByText('by Ari Chen')).toBeVisible();
2625 await expect(page.getByText('#parametric')).toBeVisible();
···3231 await expect(page.getByText('by mira.tools')).toBeVisible();
33323433 await expect(page.getByRole('heading', { name: 'Modular calibration tower' })).toBeVisible();
3535- await expect(page.getByRole('img', { name: 'Blueprint placeholder for missing preview media' })).toHaveCount(2);
3636- await expect(page.getByText('No preview')).toHaveCount(2);
3737- await expect(page.getByText('preview pending')).toHaveCount(2);
3434+ expect(await page.getByRole('img', { name: 'Blueprint placeholder for missing preview media' }).count()).toBeGreaterThanOrEqual(2);
38353936 await expect(page.getByLabel('Browse results placeholder')).toHaveCount(0);
4037 await expect(page.getByText('Empty')).toHaveCount(0);
4138});
42394343-test('global shell search navigates client-side to /search', async ({ page }) => {
4040+test('global shell search navigates to /search', async ({ page }) => {
4441 await page.goto('/');
45424646- // Gate on a rendered feed card so the wasm client has hydrated and wired the
4747- // search field's keydown handler before we type — otherwise Enter races
4848- // hydration and is dropped (the SSR input accepts fill but no handler runs).
4343+ // Gate on a rendered feed card so the shell has loaded before we use search.
4444+ // Search is a native GET form, so this works before and after hydration.
4945 await expect(page.getByLabel('Browse results').getByRole('article').first()).toBeVisible();
50465147 const search = page.getByRole('searchbox', { name: 'Search Polymodel' });
5248 await search.fill('enclosure');
5353- await search.press('Enter');
4949+ await page.getByRole('button', { name: 'Search' }).click();
54505551 await expect(page).toHaveURL(/\/search\?q=enclosure$/);
5656- await expect(page.getByRole('heading', { name: 'Search', exact: true })).toBeVisible();
5757- await expect(page.getByText('Not wired up yet')).toBeVisible();
5858- await expect(page.getByText(/Searched for/)).toBeVisible();
5252+ await expect(page.getByRole('region', { name: 'Search', exact: true })).toBeVisible();
5353+ await expect(page.getByText('"enclosure"')).toBeVisible();
5454+ await expect(page.getByLabel('Search results')).toBeVisible();
5555+ await expect(page.getByRole('heading', { name: 'Parametric enclosure kit' })).toBeVisible();
5956});
60576158test('shell header is present on a deep route', async ({ page }) => {
···68656966test('placeholder routes resolve with honest, non-faked content', async ({ page }) => {
7067 await page.goto('/search');
7171- await expect(page.getByRole('heading', { name: 'Search', exact: true })).toBeVisible();
7272- await expect(page.getByText('Not wired up yet')).toBeVisible();
6868+ await expect(page.getByRole('region', { name: 'Search', exact: true })).toBeVisible();
6969+ await expect(page.getByRole('heading', { name: 'Search from the header' })).toBeVisible();
7070+ await expect(page.getByText('Find indexed projects by name, tag, author, or description.')).toBeVisible();
7371});
74727573test('home thing cards remain readable at narrow widths', async ({ page }) => {
···8078 await expect(page.getByRole('heading', { name: 'Parametric enclosure kit' })).toBeVisible();
8179 await expect(page.getByRole('heading', { name: 'Untitled thing' })).toBeVisible();
8280 await expect(page.getByRole('heading', { name: 'Modular calibration tower' })).toBeVisible();
8383- await expect(page.getByRole('img', { name: 'Blueprint placeholder for missing preview media' })).toHaveCount(2);
8181+ expect(await page.getByRole('img', { name: 'Blueprint placeholder for missing preview media' }).count()).toBeGreaterThanOrEqual(2);
8482});
85838684test('foundation route shows foundation primitives and reusable states', async ({ page }) => {
+2-2
e2e/tests/publish.spec.ts
···1111 await page.goto('/publish');
12121313 await expect(page.getByRole('heading', { name: 'Share your models' })).toBeVisible();
1414- const callout = page.locator('section[aria-label="Sign in to publish"]');
1414+ const callout = page.locator('section[aria-label="Sign in"]');
1515 await expect(callout.getByRole('button', { name: 'Sign in' })).toBeVisible();
16161717 // The signed-out surface must not expose the wizard stepper or publish action.
···2222test('signed-out publish callout returns the user to /publish after sign-in', async ({ page }) => {
2323 await page.goto('/publish');
24242525- const returnTo = page.locator('section[aria-label="Sign in to publish"] input[name="return_to"]');
2525+ const returnTo = page.locator('section[aria-label="Sign in"] input[name="return_to"]');
2626 await expect(returnTo).toHaveValue('/publish');
2727});
+2-2
justfile
···5151e2e:
5252 cd e2e && npm test
5353# Start the Dioxus dev server.
5454-serve:
5555- dx serve
5454+serve *ARGS:
5555+ dx serve {{ ARGS }}
5656# Build the Dioxus app for web.
5757build-web:
5858 dx build --platform web
+9-3
src/indexing/config.rs
···2424 /// defaults the OAuth client origin to `https://polymodel.space`, a functional
2525 /// hosted client; set `POLYMODEL_BASE_URL` for local or alternate origins.
2626 pub base_url: Option<String>,
2727- /// Local/demo sample-data mode. `auto` seeds conservative local SQLite DBs
2828- /// only when empty; `force` seeds any SQLite DB when empty; `off` disables it.
2727+ /// Local/demo sample-data mode. Defaults to `off`; `auto` seeds
2828+ /// conservative local SQLite DBs only when empty; `force` seeds any SQLite
2929+ /// DB when empty.
2930 pub sample_data: SampleDataMode,
3031}
3132···4647 tracing::warn!(%value, "unknown POLYMODEL_SAMPLE_DATA value; falling back to off");
4748 Self::Off
4849 }
4949- None => Self::Auto,
5050+ None => Self::Off,
5051 }
5152 }
5253···112113 SampleDataMode::from_env(Some("auto".into())),
113114 SampleDataMode::Auto
114115 );
116116+ }
117117+118118+ #[test]
119119+ fn sample_data_defaults_to_off() {
120120+ assert_eq!(SampleDataMode::from_env(None), SampleDataMode::Off);
115121 }
116122}
+4-4
src/indexing/sample_data.rs
···11//! Deterministic local/demo sample data for appview read paths.
22//!
33-//! This module writes projection-shaped SQLite rows only when the local/demo
44-//! database has no Polymodel things. It lets ordinary local `just serve` and e2e
55-//! exercise the same `getFeed`, `getThing`, and `getModel` appview contracts as
66-//! production reads without UI-only fixtures.
33+//! This module writes projection-shaped SQLite rows only when explicitly enabled
44+//! and the local/demo database has no Polymodel things. It lets e2e and local
55+//! demo runs exercise the same `getFeed`, `getThing`, and `getModel` appview
66+//! contracts as production reads without UI-only fixtures.
7788use jacquard_common::types::blob::BlobRef;
99use polymodel_api::space_polymodel::{
+5-16
src/shell.rs
···5555 }
5656}
57575858-/// Global directed-search field. Submits client-side through the navigator to
5959-/// the `/search` route; it never performs a full document navigation (contrast
6060-/// `SessionControl`, which issues a real GET to `/oauth/start`).
5858+/// Global directed-search field. Uses a plain GET form so SSR/pre-hydration
5959+/// submissions still resolve to `/search?q=...`; Dioxus keeps the value synced
6060+/// with the current route once hydrated.
6161#[component]
6262fn ShellSearch() -> Element {
6363 let route = use_route::<Route>();
···7777 }
78787979 rsx! {
8080- div { class: "app-search", role: "search",
8080+ form { class: "app-search", role: "search", action: "/search", method: "get",
8181 input {
8282 class: "app-search-input",
8383 r#type: "search",
···8686 aria_label: "Search Polymodel",
8787 value: "{query}",
8888 oninput: move |event| query.set(event.value()),
8989- onkeydown: move |event| {
9090- if event.key() == Key::Enter {
9191- event.prevent_default();
9292- let q = query.read().trim().to_string();
9393- navigator().push(Route::Search { q });
9494- }
9595- },
9689 }
9790 button {
9891 class: "button button-secondary app-search-button",
9999- r#type: "button",
100100- onclick: move |_| {
101101- let q = query.read().trim().to_string();
102102- navigator().push(Route::Search { q });
103103- },
9292+ r#type: "submit",
10493 "Search"
10594 }
10695 }