-
Notifications
You must be signed in to change notification settings - Fork 127
Simplify client: remove Svelte, use pure Astro with SSR data fetching #179
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 9 commits
Commits
Show all changes
30 commits
Select commit
Hold shift + click to select a range
5e2adb4
Simplify client: remove Svelte, use pure Astro with SSR data fetching
GeekTrainer 7cfff68
Initial plan
Copilot 5006a9b
Create mock API server, update e2e tests for SSR, remove unused esbui…
Copilot 9a5fed4
Update workshop content to reflect pure Astro app (remove Svelte refe…
Copilot 28e1ba4
Replace mock API with real Flask server using seeded test database fo…
Copilot a2e5bb1
Move DATABASE_PATH resolution into seed_test_database() function
Copilot dd43328
Remove Google Fonts, use system font stack instead
Copilot 7b19f3b
Disable Astro telemetry via ASTRO_TELEMETRY_DISABLED env var in config
Copilot 74bc88a
Merge pull request #178 from github-samples/copilot/simplify-client-app
GeekTrainer ab3e09f
Fix content alignment with simplified app
GeekTrainer 6f610c0
Restructure project into app/ directory
GeekTrainer 1d58a16
Add pagination to dogs API and homepage
GeekTrainer f2d26f5
Fix Vite serving errors for symlinked node_modules
GeekTrainer d84b9ae
Remove old client/, server/, scripts/ directories
GeekTrainer 4d9a59c
Add dev tooling for Copilot sandbox environment
GeekTrainer 042df81
Genericize sandbox script with external config
GeekTrainer 1ec9322
Update content/1-hour/README.md
GeekTrainer 314144e
Update PLAN.md
GeekTrainer f83547f
Initial plan
Copilot b091dc6
Merge main, resolve conflicts, fix content path references
Copilot d11f050
Reset actions-workshop content to main, remove .dev/ and PLAN.md
GeekTrainer b278230
Fix GitHub Actions workshop content issues
GeekTrainer b56c68a
Fix 1-hour and full-day workshop content issues
GeekTrainer decf271
Merge pull request #190 from github-samples/copilot/sub-pr-179
GeekTrainer 368d84f
Update npm dependencies to latest major versions
GeekTrainer f304509
Remove adoption-site duplicate and .github/skills, gitignore .playwri…
GeekTrainer bb2575c
Address Copilot code review feedback
GeekTrainer ce14043
Merge main into simplify-client-app
GeekTrainer fa026fb
Fix working-directory paths in GitHub Actions content
GeekTrainer fdaa14a
Use platform-aware Python executable in test server script
GeekTrainer File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,95 @@ | ||
| # Plan: Simplify Client App — Pure Astro, Remove Svelte | ||
|
|
||
| ## Problem Statement | ||
|
|
||
| The client app is more complex than necessary: | ||
|
|
||
| 1. **Svelte is unnecessary** — Both Svelte components (`DogList.svelte`, `DogDetails.svelte`) have zero client-side interactivity (no state changes, no event handlers, just data display and `<a>` links). They can be simple Astro components. | ||
| 2. **Unnecessary client-side data fetching** — Components fetch data client-side via `fetch('/api/...')`, even though Astro is configured for SSR and can fetch server-side in page frontmatter | ||
| 3. **Middleware API proxy adds complexity** — The middleware intercepts `/api/` requests and proxies them to Flask. With server-side fetching in Astro, this layer is unnecessary | ||
| 4. **Dead dependencies** — `autoprefixer`, `postcss` (Tailwind v4 handles this), `flask-cors` (no cross-origin requests with SSR) | ||
| 5. **Unused assets & redundant code** — Starter template leftovers, duplicate imports, placeholder comments | ||
|
|
||
| ## Proposed Approach | ||
|
|
||
| Remove Svelte entirely. Convert Svelte components to Astro components. Fetch data server-side in Astro page frontmatter. Remove the middleware proxy, dead dependencies, and unused files. | ||
|
|
||
| ## Todos | ||
|
|
||
| ### 1. Convert `DogList.svelte` → `DogList.astro` | ||
| - **Delete**: `client/src/components/DogList.svelte` | ||
| - **Create**: `client/src/components/DogList.astro` | ||
| - Accept `dogs` array via `Astro.props` | ||
| - Render the same HTML grid of dog cards (pure template, no JS) | ||
|
|
||
| ### 2. Convert `DogDetails.svelte` → `DogDetails.astro` | ||
| - **Delete**: `client/src/components/DogDetails.svelte` | ||
| - **Create**: `client/src/components/DogDetails.astro` | ||
| - Accept `dog` object via `Astro.props` | ||
| - Render the same HTML dog detail card (pure template, no JS) | ||
|
|
||
| ### 3. Update `index.astro` — server-side data fetching | ||
| - **File**: `client/src/pages/index.astro` | ||
| - Fetch dogs list from Flask in frontmatter (`API_SERVER_URL/api/dogs`) | ||
| - Pass data to `DogList.astro` as props | ||
| - Handle error states in the page | ||
| - Remove `global.css` import (already in Layout) | ||
|
|
||
| ### 4. Update `[id].astro` — server-side data fetching | ||
| - **File**: `client/src/pages/dog/[id].astro` | ||
| - Fetch dog details from Flask in frontmatter (`API_SERVER_URL/api/dogs/{id}`) | ||
| - Pass data to `DogDetails.astro` as props | ||
| - Handle 404 / error states | ||
| - Remove redundant `export const prerender = false` and unused `props` variable | ||
|
|
||
| ### 5. Remove the API proxy middleware | ||
| - **Delete**: `client/src/middleware.ts` | ||
|
|
||
| ### 6. Remove Svelte from the project | ||
| - **File**: `client/astro.config.mjs` — Remove Svelte integration and duplicate vite plugin | ||
| - **Delete**: `client/svelte.config.js` | ||
| - **File**: `client/package.json` — Remove `svelte`, `@astrojs/svelte` | ||
|
|
||
| ### 7. Remove dead dependencies | ||
| - **File**: `client/package.json` — Remove `autoprefixer`, `postcss` (Tailwind v4 + Vite handles CSS natively) | ||
| - **File**: `server/requirements.txt` — Remove `flask-cors` (no cross-origin with SSR) | ||
|
|
||
| ### 8. Remove unused assets | ||
| - **Delete**: `client/src/assets/astro.svg`, `client/src/assets/background.svg` (starter template leftovers, not referenced anywhere) | ||
|
|
||
| ### 9. Clean up minor issues | ||
| - **Eliminate `global.css`** — It only contains `@import "tailwindcss"`. Move this into the `<style is:global>` block in `Layout.astro` and delete the file. Removes a file and 3 redundant imports. | ||
| - **Simplify Header to always-visible nav** — The hamburger menu with JS toggle is overkill for 2 nav links (Home, About). Replace with simple inline nav links. This eliminates the **only client-side JavaScript** in the entire app, making it truly zero-JS. | ||
| - **Remove unused `dark:` variants** — `<html>` has `class="dark"` hardcoded, so `dark:` prefixes are always active. The non-dark variants (`bg-blue-500`, `bg-white`, `text-slate-800`) never apply. Replace `bg-blue-500 dark:bg-blue-700` with just `bg-blue-700`, etc. Simpler for learners to read. | ||
| - **Remove `transition-colors duration-300`** — These transition classes appear on many elements but never trigger (no theme switching). Dead code. | ||
| - Remove `// about page` comment from `about.astro` | ||
|
|
||
| ### 10. Update all libraries to latest versions | ||
| - **Client**: `astro`, `@astrojs/node`, `@tailwindcss/vite`, `tailwindcss`, `typescript`, `@playwright/test`, `@types/node` | ||
| - **Server**: Pin `flask`, `sqlalchemy`, `flask_sqlalchemy` to latest stable | ||
|
|
||
| ### 11. Add data-testid attributes to all components | ||
| - Add `data-testid` attributes to key elements across all Astro components for stable test selectors | ||
| - Components: `DogList.astro`, `DogDetails.astro`, `index.astro`, `[id].astro`, `about.astro`, `Header.astro` | ||
| - Key attributes: `dog-list`, `dog-card`, `dog-name`, `dog-breed`, `dog-details`, `dog-age`, `dog-gender`, `dog-status`, `dog-description`, `error-message`, `empty-state`, `back-link` | ||
|
|
||
| ### 12. Create mock API server for e2e tests | ||
| - **Create**: `client/e2e-tests/mock-api.ts` — A lightweight Node.js HTTP server that serves the same endpoints as Flask (`/api/dogs`, `/api/dogs/:id`) with hardcoded test data | ||
| - **Update**: `client/playwright.config.ts` — Use Playwright's multiple `webServer` config to start both the mock API and Astro dev server (with `API_SERVER_URL` pointing to the mock) | ||
| - This decouples e2e tests from the Flask server entirely | ||
|
|
||
| ### 13. Update e2e tests to use data-testid selectors | ||
| - **All test files**: Replace brittle text selectors (`getByText('Buddy')`) and CSS selectors (`.grid a[href^="/dog/"]`) with `data-testid` locators | ||
| - **`api-integration.spec.ts`**: Rewrite to test against mock API server-rendered content (no more `page.route()` mocks) | ||
| - **`homepage.spec.ts`**: Remove the "loading state" test (no loading state with SSR) and the client-side API error test | ||
| - **`dog-details.spec.ts`**: Update selectors to use data-testid | ||
|
|
||
| ### 14. Run `npm install` and verify build + e2e tests | ||
|
|
||
| ## Notes | ||
|
|
||
| - The Flask server (`server/app.py`) is unchanged (logic-wise) | ||
| - `API_SERVER_URL` env var moves from middleware to a shared constant used by Astro pages | ||
| - The `Header.astro` component with its vanilla JS menu toggle is fine as-is | ||
| - E2e tests should pass since rendered HTML output is equivalent | ||
| - This eliminates the entire Svelte framework from the dependency tree | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,56 +1,47 @@ | ||
| import { test, expect } from '@playwright/test'; | ||
|
|
||
| test.describe('API Integration', () => { | ||
| test('should fetch dogs from API', async ({ page }) => { | ||
| // Mock successful API response | ||
| await page.route('/api/dogs', route => { | ||
| route.fulfill({ | ||
| status: 200, | ||
| contentType: 'application/json', | ||
| body: JSON.stringify([ | ||
| { id: 1, name: 'Buddy', breed: 'Golden Retriever' }, | ||
| { id: 2, name: 'Luna', breed: 'Husky' }, | ||
| { id: 3, name: 'Max', breed: 'Labrador' } | ||
| ]) | ||
| }); | ||
| }); | ||
|
|
||
| test('should render dogs from the API on the homepage', async ({ page }) => { | ||
| await page.goto('/'); | ||
|
|
||
| // Check that mocked dogs are displayed | ||
| await expect(page.getByText('Buddy')).toBeVisible(); | ||
| await expect(page.getByText('Golden Retriever')).toBeVisible(); | ||
| await expect(page.getByText('Luna')).toBeVisible(); | ||
| await expect(page.getByText('Husky')).toBeVisible(); | ||
| await expect(page.getByText('Max')).toBeVisible(); | ||
| await expect(page.getByText('Labrador')).toBeVisible(); | ||
|
|
||
| const dogCards = page.getByTestId('dog-card'); | ||
| await expect(dogCards).toHaveCount(3); | ||
|
|
||
| await expect(page.getByTestId('dog-name').nth(0)).toHaveText('Buddy'); | ||
| await expect(page.getByTestId('dog-breed').nth(0)).toHaveText('Golden Retriever'); | ||
|
|
||
| await expect(page.getByTestId('dog-name').nth(1)).toHaveText('Luna'); | ||
| await expect(page.getByTestId('dog-breed').nth(1)).toHaveText('Husky'); | ||
|
|
||
| await expect(page.getByTestId('dog-name').nth(2)).toHaveText('Max'); | ||
| await expect(page.getByTestId('dog-breed').nth(2)).toHaveText('German Shepherd'); | ||
| }); | ||
|
|
||
| test('should handle empty dog list', async ({ page }) => { | ||
| // Mock empty API response | ||
| await page.route('/api/dogs', route => { | ||
| route.fulfill({ | ||
| status: 200, | ||
| contentType: 'application/json', | ||
| body: JSON.stringify([]) | ||
| }); | ||
| }); | ||
| test('should render dog details from the API', async ({ page }) => { | ||
| await page.goto('/dog/1'); | ||
|
|
||
| await page.goto('/'); | ||
|
|
||
| // Check that empty state message is displayed | ||
| await expect(page.getByText('No dogs available at the moment')).toBeVisible(); | ||
| await expect(page.getByTestId('dog-details')).toBeVisible(); | ||
| await expect(page.getByTestId('dog-name')).toHaveText('Buddy'); | ||
| await expect(page.getByTestId('dog-breed')).toContainText('Golden Retriever'); | ||
| await expect(page.getByTestId('dog-age')).toContainText('3'); | ||
| await expect(page.getByTestId('dog-gender')).toContainText('Male'); | ||
| await expect(page.getByTestId('dog-status')).toHaveText('Available'); | ||
| }); | ||
|
|
||
| test('should handle network errors', async ({ page }) => { | ||
| // Mock network error | ||
| await page.route('/api/dogs', route => { | ||
| route.abort('failed'); | ||
| }); | ||
| test('should return 404 details for non-existent dog', async ({ page }) => { | ||
| await page.goto('/dog/99999'); | ||
|
|
||
| await expect(page.getByTestId('error-message')).toBeVisible(); | ||
| await expect(page.getByTestId('error-message')).toContainText('not found'); | ||
| }); | ||
|
|
||
| test('should link from dog card to detail page', async ({ page }) => { | ||
| await page.goto('/'); | ||
|
|
||
| // Check that error message is displayed | ||
| await expect(page.getByText(/Error:/)).toBeVisible({ timeout: 10000 }); | ||
|
|
||
| const firstCard = page.getByTestId('dog-card').first(); | ||
| await firstCard.click(); | ||
|
|
||
| await expect(page).toHaveURL(/\/dog\/1$/); | ||
| await expect(page.getByTestId('dog-details')).toBeVisible(); | ||
| }); | ||
| }); | ||
| }); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.