Navigation
Each surface has its own navigation stack, kept inside the app. The first screen is named
'index' and its params are the surface params. push adds a screen, pop removes the top one,
replace swaps the top one. Olan is told about every change so it can show its own back control;
when the user presses back (or swipes back on iOS, or uses the Android back button), Olan pops the
stack.
The pattern: one surface component reads useNavigation() and renders the visible screen.
import { List, Screen, defineApp, olan, useNavigation, useQuery } from '@olan-app/sdk';
function ProjectsScreen() {
const navigation = useNavigation();
const projects = useQuery(() => olan.projects.list({ limit: 50 }));
const items = (projects.data?.items ?? []).map((p) => ({ key: p.id, title: p.name }));
return (
<Screen title="Projeler" loading={projects.loading}>
<List items={items} onSelect={({ key }) => navigation.push('project', { projectId: String(key) })} />
</Screen>
);
}
function ProjectScreen({ projectId }: { projectId: string }) {
const detail = useQuery(() => olan.projects.get(projectId), [projectId]);
const items = (detail.data?.tasks ?? []).map((t) => ({ key: t.id, title: t.title }));
return (
<Screen title={detail.data?.project.name ?? 'Proje'} loading={detail.loading}>
<List items={items} emptyText="Görev yok" />
</Screen>
);
}
function App() {
const { screen, params } = useNavigation();
const p = (params ?? {}) as { projectId?: string };
if (screen === 'project' && p.projectId) return <ProjectScreen projectId={p.projectId} />;
return <ProjectsScreen />;
}
export default defineApp({ app: App });
Rules:
- Screen names and params are yours; params must be JSON (strings, numbers, booleans, null, arrays, objects).
- Each screen component gets fresh state when it is pushed; screens below keep nothing (they re-render from their queries when they become visible again).
- Do not add your own "Geri" (back) button for the stack; Olan shows one. A button that calls
pop()after finishing a task (e.g. after a form submit) is fine. pop()on the first screen keeps the stack; Olan closes a modal surface, other surfaces stay open.project.widgetsurfaces are small cards; keep them to one screen.- An app address (
https://<Olan web>/apps/a/<appId>/<screen>?id=…, SDK 1.5, links.md) opens theappsurface on that screen: the stack starts as[index, <screen>]with the query as the screen's params (indexalone gets them as its params), and back returns toindex. So read every screen's params defensively: they may come from a link, as strings.
Testing navigation (testing.md):
const app = await renderApp();
await app.press('Lansman'); // List item title
app.navigation(); // [{ action: 'push', screen: 'project', params: { projectId: 'launch' } }]
await app.back(); // the host's back control