Welcome to the final installment. Everything you learned now converges into Task Orbit, a small task manager that stitches together routing, state, accessibility, and deployment.
Before you begin
- What you build: a Task Orbit mini app with
/(home),/stats, and/activityroutes that integrates server actions, stores, accessibility announcements, and deployment scripts. - Why it matters: chaining chapters 1–19 into one flow cements the patterns so you can reuse them in real projects.
- Watch out: lock your folder structure first, then document how server actions, stores, and routes exchange data to avoid knots during refactors.
Key terms
- Capstone: an end-to-end project that applies every technique—from design to deployment—in one run.
- Routing: picking which page component to render for
/stats,/activity, and other paths. - Accessibility live region: an
aria-livearea or screen-reader-only block that announces updates without a visible UI change.
Core ideas
A capstone glues together previously isolated lessons. State represents the data your components remember, derived stores compute additional values, routing swaps screens based on URLs, and accessibility ensures everyone can use the app—including assistive technology users.
Understanding the Task Orbit App Map
👉 Mini recap: the layout checks the session, each page server module gathers its data, and every screen consumes the shared tasks store. /stats adds derived metrics, while /activity bolts on an SSE subscription.
Code tour
Start with the folder layout and key modules.
lib/
stores/tasks.ts
router.ts
accessibility.ts
server/
notifications.ts
routes/
+layout.svelte
+layout.server.ts
+page.svelte
+page.server.ts
stats/+page.svelte
stats/+page.server.ts
activity/+page.svelte
api/tasks/+server.ts
components/
TaskForm.svelte
TaskList.svelte
StatCard.svelte
ActivityLog.svelte
app.d.ts
// src/lib/stores/tasks.ts
type Task = {
id: string;
title: string;
priority: 'low' | 'medium' | 'high';
done: boolean;
createdAt: number;
};
export const tasks = writable<Task[]>([]);
export const pendingTasks = derived(tasks, ($tasks) =>
$tasks.filter((task) => !task.done)
);
export const stats = derived(tasks, ($tasks) => ({
total: $tasks.length,
done: $tasks.filter((task) => task.done).length,
ratio: $tasks.length
? Math.round(($tasks.filter((task) => task.done).length / $tasks.length) * 100)
: 0
}));
<!-- src/components/TaskForm.svelte -->
<script lang="ts">
import { enhance } from '$app/forms';
import { invalidate } from '$app/navigation';
export let action = '?/create';
</script>
<form method="POST" {action}
use:enhance={({ result }) => {
if (result.type === 'success') invalidate('data:tasks');
}}>
<label>
Title
<input name="title" placeholder="New task" required />
</label>
<label>
Priority
<select name="priority">
<option value="low">Low</option>
<option value="medium" selected>Medium</option>
<option value="high">High</option>
</select>
</label>
<button type="submit">Add</button>
</form>
<!-- src/routes/+page.svelte -->
<script lang="ts">
import TaskForm from '$components/TaskForm.svelte';
import TaskList from '$components/TaskList.svelte';
export let data;
</script>
<TaskForm />
<TaskList tasks={data.tasks} />
<section aria-live="polite" class="sr-only">
Completion rate {data.stats.ratio}%
</section>
// src/routes/+page.server.ts
export const load = async ({ fetch, depends }) => {
depends('data:tasks');
const [tasks, stats] = await Promise.all([
fetch('/api/tasks').then((r) => r.json()),
fetch('/api/tasks/stats').then((r) => r.json())
]);
return { tasks, stats };
};
export const actions = {
create: async ({ request, fetch }) => {
const form = Object.fromEntries(await request.formData());
if (!form.title) {
return fail(400, { errors: { title: 'Title is required.' } });
}
const res = await fetch('/api/tasks', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(form)
});
if (!res.ok) {
return fail(500, { errors: { title: 'Could not save the task.' } });
}
return { success: true };
}
};
// src/lib/router.ts
export const currentSection = derived(page, ($page) => $page.url.pathname.replace('/', '') || 'home');
export function navigate(section: string) {
goto(section === 'home' ? '/' : `/${section}`);
}
pnpm run lint
pnpm run test
pnpm run check
pnpm run build
Ship to Vercel or Netlify once linting, tests, and type checks are green.
💬 댓글
이 글에 대한 의견을 남겨주세요