From 5ee0b8e0859889284474310de779ba9d820c89e2 Mon Sep 17 00:00:00 2001 From: Kato Hiroki Date: Fri, 2 Jan 2026 14:18:19 +0000 Subject: [PATCH 01/62] test: Add e2e tests before migration libs (#2054) --- .../component-mapping.md | 362 +++++++++++ .../plan.md | 576 ++++++++++++++++++ .../smoke-tests.md | 465 ++++++++++++++ tests/custom-colors.spec.ts | 75 +++ tests/dark-mode.spec.ts | 76 +++ tests/navbar.spec.ts | 56 ++ 6 files changed, 1610 insertions(+) create mode 100644 docs/dev-notes/2026-01-02/migrate-from-svelte-5-ui-lib-to-flowbite-svelte/component-mapping.md create mode 100644 docs/dev-notes/2026-01-02/migrate-from-svelte-5-ui-lib-to-flowbite-svelte/plan.md create mode 100644 docs/dev-notes/2026-01-02/migrate-from-svelte-5-ui-lib-to-flowbite-svelte/smoke-tests.md create mode 100644 tests/custom-colors.spec.ts create mode 100644 tests/dark-mode.spec.ts create mode 100644 tests/navbar.spec.ts diff --git a/docs/dev-notes/2026-01-02/migrate-from-svelte-5-ui-lib-to-flowbite-svelte/component-mapping.md b/docs/dev-notes/2026-01-02/migrate-from-svelte-5-ui-lib-to-flowbite-svelte/component-mapping.md new file mode 100644 index 000000000..d32ccdb8b --- /dev/null +++ b/docs/dev-notes/2026-01-02/migrate-from-svelte-5-ui-lib-to-flowbite-svelte/component-mapping.md @@ -0,0 +1,362 @@ +# コンポーネント対応マトリクス + +svelte-5-ui-lib → Flowbite Svelte 移行時の各コンポーネント難易度と対応方法。 + +--- + +## 概要 + +コンポーネントを以下の4カテゴリに分類。各カテゴリの対応手数と注意点を記載。 + +--- + +## カテゴリ1:ライブラリ名置き換えのみ(⭐ 難易度低) + +| コンポーネント | svelte-5-ui-lib | Flowbite Svelte | 対応内容 | 注意点 | +| ---------------- | --------------- | --------------- | --------------------------- | ----------------------------- | +| `Heading` | ✅ | ✅ | import のみ変更 | `tag` prop 互換 | +| `P` | ✅ | ✅ | import のみ変更 | - | +| `Label` | ✅ | ✅ | import のみ変更 | - | +| `Input` | ✅ | ✅ | import のみ変更 | - | +| `Hr` | ✅ | ✅ | import のみ変更 | - | +| `Img` | ✅ | ✅ | import のみ変更 | - | +| `List` | ✅ | ✅ | import のみ変更 | slot 互換 | +| `Li` | ✅ | ✅ | import のみ変更 | - | +| `Helper` | ✅ | ✅ | import のみ変更 | - | +| `Badge` | ✅ | ✅ | import のみ変更 | `color` prop 互換 | +| `Avatar` | ✅ | ✅ | import のみ変更 | `src`, `size` prop 互換 | +| `Breadcrumb` | ✅ | ✅ | import のみ変更 | component 階層互換 | +| `BreadcrumbItem` | ✅ | ✅ | import のみ変更 | slot 互換 | +| `Table` | ✅ | ✅ | import のみ変更 | - | +| `TableHeadCell` | ✅ | ✅ | import のみ変更 | - | +| `TableBodyCell` | ✅ | ✅ | import のみ変更 | - | +| `TableBodyRow` | ✅ | ✅ | import のみ変更 | - | +| `Button` | ✅ | ✅ | import のみ変更 + props確認 | size, color, variant 系を確認 | +| `Card` | ✅ | ✅ | import のみ変更 | slot 互換 | +| `Alert` | ✅ | ✅ | import のみ変更 | `color` prop 互換 | + +**対応方法:** + +```typescript +// Before +import { Heading, Button, Label } from 'svelte-5-ui-lib'; + +// After +import { Heading, Button, Label } from 'flowbite-svelte'; +``` + +**テスト:** Vitest snapshot または Playwright (コンポーネント render 確認) + +**参考:** [Flowbite Svelte Components](https://flowbite-svelte.com/docs/components/) + +--- + +## カテゴリ2:置き換え + 属性調整(⭐⭐ 難易度中) + +| コンポーネント | 変更内容 | 詳細 | 参考 | +| ------------------ | --------------------------- | -------------------------------------------------------------- | ------------------------------------------------------------------------- | +| `Tabs` + `TabItem` | import + slot 名確認 | API は同一だが、slot 名が異なる可能性 | [Flowbite Tabs](https://flowbite-svelte.com/docs/components/tabs) | +| `Tooltip` | import + `triggeredBy` prop | v3: `content` prop / v5: `triggeredBy` で target selector 指定 | [Flowbite Tooltip](https://flowbite-svelte.com/docs/components/tooltip) | +| `Checkbox` | import + `bind:checked` | Svelte v5 runes: `bind:checked` で直接管理 | [Flowbite Checkbox](https://flowbite-svelte.com/docs/components/checkbox) | +| `Radio` | import + `bind:group` | Svelte v5 runes: `bind:group` で group 管理 | [Flowbite Radio](https://flowbite-svelte.com/docs/components/radio) | +| `Toggle` | import + `bind:checked` | Svelte v5 runes: `bind:checked` で state 管理 | [Flowbite Toggle](https://flowbite-svelte.com/docs/components/toggle) | + +**対応例:Tabs** + +```svelte + + + + + + Content 1 + + + + + + + + + Content 1 + + +``` + +**対応例:Checkbox** + +```svelte + + + + + + + +``` + +**テスト:** Vitest props + event テスト + +--- + +## カテゴリ3:外部ライブラリからの復帰(⭐⭐ 難易度中) + +| コンポーネント | 現在の対応 | Flowbite Svelte での対応 | 変更内容 | +| -------------- | ---------------------------------- | ----------------------------------- | ----------------------------------------------- | +| `Carousel` | `embla-carousel-svelte` (外部導入) | ✅ Native Flowbite Svelte component | embla 削除、Flowbite Svelte Carousel へ置き換え | + +**対応例:Carousel** + +```typescript +// Before(embla-carousel-svelte) +import { Carousel } from 'embla-carousel-svelte'; + +// After(Flowbite Svelte) +import { Carousel, Controls, CarouselIndicators } from 'flowbite-svelte'; +``` + +**詳細:** [Flowbite Carousel](https://flowbite-svelte.com/docs/components/carousel) + +**テスト:** Playwright slide navigation test + +--- + +## カテゴリ4:抜本的な書き直し必要(⭐⭐⭐ 難易度高) + +### 4-1. Dropdown(最優先対応) + +**差分の大きさ:** 🔴 高 + +**svelte-5-ui-lib:** + +```svelte + + + + + + Item 1 + + +``` + +**Flowbite Svelte:** + +```svelte + + + + + + Item 1 + (isOpen = false)}>Item 2 + +``` + +**主な変更点:** + +- `DropdownUl` / `DropdownLi` → `DropdownItem` に統合 +- `uiHelpers()` → `$state(isOpen)` runes で管理 +- `bind:isOpen` でバインド +- slot ではなく component の直接配置 + +**参考:** [Flowbite Dropdown](https://flowbite-svelte.com/docs/components/dropdown) + +--- + +### 4-2. Modal + +**差分の大きさ:** 🟡 中(native `` ベース) + +**主な変更点:** + +- native HTML `` element ベース +- `form` prop で内部フォーム自動生成 +- `bind:open` でバインド +- `onaction` callback で submit/cancel 処理 +- `uiHelpers()` 不要(フォーカストラップ、outside click は native で処理) + +**Flowbite Svelte:** + +```svelte + + + + + { + if (action === 'accept') { + console.log('Accepted'); + } + }} +> +

Modal content

+ {#snippet footer()} + + + {/snippet} +
+``` + +**参考:** [Flowbite Modal](https://flowbite-svelte.com/docs/components/modal) + +--- + +### 4-3. Toast + +**差分の大きさ:** 🟡 中 + +**主な変更点:** + +- `ToastContainer` で位置管理(top-right, bottom-left 等) +- auto-dismiss は手動実装(setTimeout) +- `transition` props で Svelte transitions 対応 + +**参考:** [Flowbite Toast](https://flowbite-svelte.com/docs/components/toast) + +--- + +### 4-4. Spinner + +**差分の大きさ:** ⭐ 低(置き換えのみ) + +**主な変更点:** + +- `type`: "default", "dots", "bars", "pulse", "orbit" +- `color`: "primary", "green", "red", "yellow" 等 +- `size`: "4", "6", "8" + +**参考:** [Flowbite Spinner](https://flowbite-svelte.com/docs/components/spinner) + +--- + +### 4-5. ButtonGroup + +**差分の大きさ:** ⭐ 低(ラッパー) + +**Flowbite Svelte:** + +```svelte + + + + + + + +``` + +**参考:** [Flowbite ButtonGroup](https://flowbite-svelte.com/docs/components/button-group) + +--- + +### 4-6. Footer / FooterCopyright + +**差分の大きさ:** 🟡 中(variants) + +**Flowbite Svelte:** + +```svelte + + +
+ + + About + Privacy Policy + +
+``` + +**参考:** [Flowbite Footer](https://flowbite-svelte.com/docs/components/footer) + +--- + +## uiHelpers 廃止への対応 + +`svelte-5-ui-lib` の `uiHelpers()` は以下で使用: + +| 使用箇所 | svelte-5-ui-lib | Flowbite Svelte での代替 | +| -------------- | -------------------------------- | -------------------------------------- | +| Modal state | `uiHelpers()` で open/close 管理 | `$state(open)` runes + `bind:open` | +| Dropdown state | `uiHelpers()` で open/close 管理 | `$state(isOpen)` runes + `bind:isOpen` | +| Focus trap | `uiHelpers()` で実装 | native `` が自動処理 | +| Outside click | `uiHelpers()` で実装 | Floating UI で自動処理 | +| Scroll lock | `uiHelpers()` で実装 | native `` が自動処理 | + +**置き換え方針:** + +- Modal / Dropdown の state は Svelte v5 `$state` runes で管理 +- フォーカストラップ、outside click は Flowbite Svelte が自動処理 + +--- + +## Svelte v4 → v5 runes への書き換え + +移行時に v5 runes 記法への統一が必須。 + +| v4 | v5 | 用途 | +| ---------------------------------------- | ----------------------------------------------------------- | -------------------- | +| `let count = 0; $: doubled = count * 2;` | `let count = $state(0); let doubled = $derived(count * 2);` | 反応性、派生状態 | +| `let open = false;` (with event) | `let open = $state(false);` (with event) | state 管理 | +| `onMount(...)` | `$effect(() => { ... })` | ライフサイクル | +| `if (browser) { ... }` | `$effect()` の中でブラウザチェック | クライアント専用処理 | + +**参考:** + +- [Svelte 5 Runes Documentation](https://svelte.dev/docs/svelte-5-migration-guide) +- [PR #1731 (v4→v5 書き換え例)](https://github.com/AtCoder-NoviSteps/AtCoderNoviSteps/pull/1731) + +--- + +## 参考資料 + +### Flowbite Svelte 公式ドキュメント + +- [Components Overview](https://flowbite-svelte.com/docs/components/) +- [TypeScript API Reference](https://flowbite-svelte.com/docs/pages/typescript) +- [GitHub Repository](https://github.com/themesberg/flowbite-svelte) + +### Svelte 関連 + +- [Svelte 5 Runes Guide](https://svelte.dev/docs/svelte-5-migration-guide) +- [Svelte 5 API Reference](https://svelte.dev/docs) + +### 移行ガイド + +- [メイン計画ドキュメント](./plan.md) +- [Smoke Tests ガイド](./smoke-tests.md) + +--- + +**作成日:** 2026-01-02 +**最終更新:** 2026-01-02 +**ステータス:** ドラフト完成 diff --git a/docs/dev-notes/2026-01-02/migrate-from-svelte-5-ui-lib-to-flowbite-svelte/plan.md b/docs/dev-notes/2026-01-02/migrate-from-svelte-5-ui-lib-to-flowbite-svelte/plan.md new file mode 100644 index 000000000..64ced2ec8 --- /dev/null +++ b/docs/dev-notes/2026-01-02/migrate-from-svelte-5-ui-lib-to-flowbite-svelte/plan.md @@ -0,0 +1,576 @@ +# UIライブラリ移行計画:svelte-5-ui-lib → Flowbite Svelte + +**作成日**: 2026-01-02 +**ステータス**: 計画フェーズ(テスト戦略・TailwindCSS v4移行 確定版) +**前提**: TailwindCSS v3→v4 移行は不可避(Flowbite Svelte v1.31.0 対応のため) + +--- + +## 概要 + +現在 `svelte-5-ui-lib@0.12.2` に依存し、UI の状態管理を `uiHelpers()` や独自の action で実装している。これを **Flowbite Svelte v1.31.0** に統一し、保守性と開発効率を向上させる。 + +### スコープと制約 + +- **必要な変更**: UI ライブラリの置換に限定(デザイン刷新ではなく等価な機能を保証) +- **優先導線**: navbar, dropdown, modal を保護対象 +- **テスト戦略**: Playwright 中心(E2E smoke test),Vitest はコンポーネント unit テスト +- **重点確認**: 前回失敗ポイント(TailwindCSS colors 未反映、navbar responsive 破壊)を重点テスト + +--- + +## 重要な前提 + +### 前回の失敗(TailwindCSS v3→v4 試行時) + +**観測内容:** + +- ビルドは成功したが **UI が崩壊** +- カスタム colors(`primary`, `atcoder`)が反映されない +- navbar のレスポンシブが動作しない +- 前回失敗ドキュメント: `docs/dev-notes/2025-12-30/bump-tailwindcss-from-v3-to-v4/plan.md` + +**最有力仮説:** + +- `tailwind.config.ts` が v4 で読み込まれていない +- `src/app.css` が v4 記法に完全移行していない +- `@source` ディレクティブでコンポーネント検索パスが設定されていない + +### 本計画での対応 + +この計画では **テストを先行実装**し、TailwindCSS v4 で カスタム colors が効くことを確認してから、UI ライブラリ置き換えに進む。 + +--- + +## フェーズ詳細 + +### フェーズ-1:テスト環境確認 + テストコード作成 + +**目的:** 前回失敗ポイント(colors, navbar responsive, dark mode toggle)を検出するテストを先に書く + +**テスト対象:** + +1. ✅ カスタム color(`primary-500`, `atcoder-Q1` など)が CSS に生成されるか +2. ✅ `xs: 420px` breakpoint が CSS に生成されるか +3. ✅ Dark mode toggle button が DOM に存在し visible であるか +4. ✅ Dark mode toggle が動作する(dark class switch)か +5. ✅ Navbar が lg 以上で visible であるか + +**テスト環境の決定:** + +``` +推奨順: +1. Playwright E2E test (ブラウザで実際に見える部分を確認) + → ブラウザで colors 生成確認、responsive 確認に最適 + → ダークモード toggle も実装が簡単 + → 既に導入済み + +2. Vitest browser mode (フォールバック) + → vitest.config.ts の browser mode 設定が必須 + → 現状不明なため、playwright を優先 +``` + +**実装:** + +- Playwright での smoke test スクリプト作成 +- セレクタ確認(HTML で navbar, dark toggle の実装を確認) +- テストコード例は `smoke-tests.md` を参照 + +**出力:** + +- `tests/navbar.spec.ts` (新規) +- `tests/custom-colors.spec.ts` (新規) + +**Gate チェック:** + +``` +全テストが PASS するまで次フェーズに進まない +- NG の場合は原因特定 → フェーズ0 の TailwindCSS 設定に戻る +``` + +**工数:** 1-2 days + +--- + +### フェーズ0:TailwindCSS v4 移行 + +**目的:** Tailwind v4 環境を整え、カスタム colors と breakpoints が機能する状態にする + +#### 0-1. TailwindCSS v4 breaking changes(必須対応) + +以下の **必須** 変更を実装: + +**1. `src/app.css` の記法変更(v3→v4)** + +v3: + +```css +@tailwind base; +@tailwind components; +@tailwind utilities; +``` + +v4: + +```css +@import 'tailwindcss'; +@plugin 'flowbite/plugin'; +@custom-variant dark (&:where(.dark, .dark *)); +``` + +**詳細は:** [Tailwind CSS Upgrade Guide v4](https://tailwindcss.com/docs/upgrade-guide) + +--- + +**2. `tailwind.config.ts` の @config 設定** + +v4 では `tailwind.config.ts` を自動認識しない場合がある。 + +```css +/* src/app.css に追加 */ +@config "../tailwind.config.ts"; +``` + +--- + +**3. `@source` ディレクティブの追加(重要)** + +現在、`tailwind.config.ts` の `content` で svelte-5-ui-lib を指定: + +```typescript +content: [ + './src/**/*.{html,js,svelte,ts}', + './node_modules/svelte-5-ui-lib/**/*.{html,js,svelte,ts}', // ← これをremoveする +], +``` + +v4 では CSS の `@source` で指定: + +```css +/* src/app.css */ +@source "../src"; +@source "../node_modules/flowbite-svelte/dist"; +@source "../node_modules/flowbite-svelte-icons/dist"; +``` + +--- + +**4. `postcss.config.mjs` の確認** + +```javascript +export default { + plugins: { + '@tailwindcss/postcss': {}, + }, +}; +``` + +--- + +#### 0-2. TailwindCSS v4 任意対応(実装は後回し) + +以下は「推奨」だが、v4 必須ではない。**UI が安定した後に** 実装: + +- ❌ CSS-first 化(utility classes → pure CSS variables) +- ❌ autoprefixer 削除(v4 では自動処理) +- ❌ postcss.config.mjs への完全統合 + +--- + +#### 0-3. Gate チェック:TailwindCSS 設定確認 + +```bash +# ビルド実行 +pnpm build + +# CSS が生成されたか確認 +cat dist/bundle.css | grep "\.text-primary-500" +cat dist/bundle.css | grep "\.bg-atcoder" +cat dist/bundle.css | grep "@media (max-width: 420px)" +``` + +**確認項目:** + +- [ ] `text-primary-500` が CSS に含まれる +- [ ] `bg-atcoder-Q1` 等が CSS に含まれる +- [ ] `@media (max-width: 420px)` が CSS に含まれる +- [ ] dev server 起動時に UI が崩れていない(目視確認) + +**合格基準:** + +- ✅ すべての確認項目が OK +- ✅ フェーズ-1 の Playwright test が PASS + +**不合格時:** + +- `tailwind.config.ts` の `@config` 設定を確認 +- `src/app.css` の `@source` 指定を確認 +- ビルドキャッシュをクリア: `rm -rf dist .svelte-kit` + +**工数:** 1-2 days + +--- + +### フェーズ1:UI ライブラリ置き換え + +**目的:** svelte-5-ui-lib のコンポーネントを Flowbite Svelte に置き換える + +#### 1-1. コンポーネント対応表の確認 + +コンポーネント毎の置き換え難度は `component-mapping.md` を参照。 + +**置き換え順序(難度順):** + +1. **カテゴリ1(⭐ 置き換えのみ)** - 約20個 → 機械的置き換え +2. **カテゴリ2(⭐⭐ 属性調整)** - 約10個 → props 確認+修正 +3. **カテゴリ3(⭐⭐ 外部ライブラリ復帰)** - Carousel → embla 削除 +4. **カテゴリ4(⭐⭐⭐ 抜本的書き直し)** - Dropdown, Modal, Toast 等 → API 理解+実装 + +#### 1-2. 段階的実装 + +**段階 1-1a: カテゴリ1 一括置き換え** + +```bash +# セマンティックな置き換え(コンテキスト確認) +find src -name "*.svelte" -type f \ + | xargs grep -l "from 'svelte-5-ui-lib'" \ + | head -20 # 最初の20ファイル確認 + +# コンポーネント毎に確認しながら置き換え +# 例: Heading → Heading, Button → Button(API同一) +``` + +**テスト実行(各段階毎):** + +```bash +pnpm test:unit # Vitest +pnpm playwright test tests/navbar.spec.ts # Playwright +pnpm playwright test tests/custom-colors.spec.ts # Playwright +``` + +**工数:** 1-2 days + +--- + +**段階 1-2: カテゴリ2 属性調整** + +各コンポーネント毎に props を確認: + +- `Tooltip`: `triggeredBy` prop で位置付け(`trigger` → `triggeredBy`) +- `Checkbox`, `Radio`: `bind:checked` → Svelte v5 runes 対応 +- `Toggle`: `bind:checked` + `checked` prop + +**参考:** Flowbite Svelte 公式ドキュメント https://flowbite-svelte.com/docs/components/ + +**工数:** 2-3 days + +--- + +**段階 1-3: Carousel 置き換え** + +`embla-carousel-svelte` → `Flowbite Svelte Carousel` へ置き換え + +**API 差分:** + +- v4: `bind:index` で slide 管理 +- `Controls`, `CarouselIndicators` コンポーネントの組み合わせ + +**参考:** https://flowbite-svelte.com/docs/components/carousel + +**工数:** 1-2 days + +--- + +**段階 1-4: 複雑なコンポーネント(Dropdown, Modal, Toast)** + +- `Dropdown`: v5 runes `$state(isOpen)` で管理 +- `Modal`: native `` + `form` prop + `onaction` callback +- `Toast`: `ToastContainer` で位置管理、auto-dismiss は手動 +- `Spinner`, `ButtonGroup`, `Footer`: シンプル置き換え + +**参考:** Flowbite Svelte GitHub Repository + +- https://github.com/themesberg/flowbite-svelte/blob/main/src/lib/dropdown/Dropdown.svelte +- https://github.com/themesberg/flowbite-svelte/blob/main/src/lib/modal/Modal.svelte + +**工数:** 3-4 days + +--- + +**段階 1-5: `uiHelpers` 廃止への対応** + +svelte-5-ui-lib で使用: + +```typescript +import { Modal, uiHelpers } from 'svelte-5-ui-lib'; +``` + +Flowbite Svelte では `uiHelpers` 不要: + +```typescript +import { Modal } from 'flowbite-svelte'; + +// 状態管理は $state で +let modalOpen = $state(false); + +// submit/cancel は onaction callback で + { ... }}> +``` + +**工数:** 含(各段階毎に対応) + +--- + +### フェーズ2:テスト実行 + 確認 + +**目的:** UI ライブラリ置き換え後、前回失敗ポイントのリグレッション検出 + +#### 2-1. Playwright smoke test 再実行 + +```bash +pnpm playwright test tests/navbar.spec.ts --headed +pnpm playwright test tests/custom-colors.spec.ts --headed +``` + +**期待結果:** + +- ✅ カスタム colors が反映されている(button, badge 色が正しい) +- ✅ Navbar が lg で visible、mobile で隠れている +- ✅ Dark mode toggle ボタンが表示され、動作する + +**NG の場合:** + +- コンポーネント props 確認(color prop 名が変わっていないか) +- CSS class 名確認(Flowbite Svelte のデフォルト class との差分) + +--- + +#### 2-2. 手動確認(必須) + +```bash +pnpm dev +# 以下を目視確認: +# - 主要ページ(トップ、問題一覧、コンテスト)の見た目 +# - ダークモード ON/OFF での見た目 +# - モバイル(420px)での見た目 +# - Dropdown, Modal, Tooltip の開閉動作 +``` + +--- + +#### 2-3. 既存テストスイート実行 + +```bash +pnpm test:unit # Vitest +pnpm test:integration # Playwright 全テスト +``` + +**工数:** 2-3 days + +--- + +### フェーズ3:svelte-5-ui-lib 依存削除 + +全置き換え完了後: + +```bash +pnpm remove svelte-5-ui-lib +``` + +**確認:** + +```bash +pnpm install +pnpm build +pnpm test:unit +pnpm test:integration +``` + +**工数:** <1 day + +--- + +## TailwindCSS v4 Breaking Changes(詳細) + +### 必須変更 + +| 項目 | v3 | v4 | 対応 | +| ------------------- | ---------------------- | --------------------------------- | ------- | +| **CSS directives** | `@tailwind base;` | `@import "tailwindcss";` | ✅ 必須 | +| **Plugin 指定** | tailwind.config.ts | CSS の `@plugin` directive | ✅ 必須 | +| **content path** | `content: [...]` | CSS の `@source` directive | ✅ 必須 | +| **Config 読み込み** | 自動 | `@config "../tailwind.config.ts"` | ✅ 必須 | +| **Dark mode** | `darkMode: 'selector'` | CSS の `@custom-variant dark` | ✅ 必須 | + +### 任意・推奨(実装は後回し) + +| 項目 | 説明 | 優先度 | +| --------------------- | ------------------------------- | --------- | +| **CSS Variables** | Theme colors を CSS vars に変換 | 🟠 後回し | +| **autoprefixer 削除** | v4 が自動処理 | 🟠 後回し | +| **@layer の活用** | CSS-first approach | 🟠 後回し | + +**参考:** [Tailwind CSS v4 Upgrade Guide](https://tailwindcss.com/docs/upgrade-guide) + +--- + +## 確認事項 + Gate + +### Gate 1:TailwindCSS v4 設定確認 + +**実施時期:** フェーズ0 完了時 + +``` +条件: +- [ ] `text-primary-500` がビルド CSS に含まれる +- [ ] `bg-atcoder-Q1` 等がビルド CSS に含まれる +- [ ] `@media (max-width: 420px)` がビルド CSS に含まれる +- [ ] dev server での目視確認で UI 崩れなし + +合格:すべて ✅ → フェーズ1 へ進行 +不合格:1つでも ❌ → フェーズ0 に戻り原因特定 +``` + +--- + +### Gate 2:Playwright smoke test 合格 + +**実施時期:** フェーズ1 各段階の後、フェーズ2 開始時 + +```bash +pnpm playwright test tests/navbar.spec.ts +pnpm playwright test tests/custom-colors.spec.ts +``` + +``` +条件: +- [ ] Dark mode button visible test: PASS +- [ ] Dark mode toggle test: PASS +- [ ] Navbar lg visible test: PASS +- [ ] Custom colors test: PASS + +合格:すべて ✅ → 次フェーズへ +不合格:1つでも ❌ → 該当するコンポーネントを修正 +``` + +--- + +## スケジュール概算 + +| フェーズ | 内容 | 工数 | リスク | +| -------- | ------------------------------- | -------------- | ------------------------- | +| **-1** | テスト作成 + 環境確認 | 1-2 days | 🟡 中(playwright setup) | +| **0** | TailwindCSS v4 breaking changes | 1-2 days | 🔴 高(config ハマり) | +| **1-1** | カテゴリ1 置き換え | 1-2 days | ⭐ 低 | +| **1-2** | カテゴリ2 属性調整 | 2-3 days | 🟡 中 | +| **1-3** | Carousel 置き換え | 1-2 days | 🟡 中 | +| **1-4** | 複雑コンポーネント | 3-4 days | 🔴 高 | +| **2** | テスト実行 + 確認 | 2-3 days | 🟠 中(デバッグ) | +| **3** | 依存削除 | <1 day | ⭐ 低 | +| **合計** | | **12-19 days** | | + +--- + +## 参考資料 + +### Tailwind CSS v4 + +- [Tailwind CSS Upgrade Guide](https://tailwindcss.com/docs/upgrade-guide) +- [v4 Breaking Changes](https://tailwindcss.com/docs/upgrade-guide#changes-from-v3) + +### Flowbite Svelte + +- [Flowbite Svelte GitHub](https://github.com/themesberg/flowbite-svelte) +- [Flowbite Svelte Docs](https://flowbite-svelte.com/docs/components/) +- [Component API Reference](https://flowbite-svelte.com/docs/pages/typescript) + +### 前回失敗ドキュメント + +- [TailwindCSS v3→v4 試行時の記録](../2025-12-30/bump-tailwindcss-from-v3-to-v4/plan.md) + +### テスト詳細 + +- [Smoke Tests ガイド](./smoke-tests.md) +- [Component Mapping](./component-mapping.md) + +--- + +## フェーズ-1 実装結果と教訓(2026-01-02) + +### 実装内容 + +- ✅ `tests/custom-colors.spec.ts` 実装:ビルド出力の CSS ファイルをチェック +- ✅ `tests/dark-mode.spec.ts` 実装:ダークモード toggle 動作確認 +- ✅ `tests/navbar.spec.ts` 実装:navbar レスポンシブ確認 +- ✅ smoke-tests.md のセレクタを確定 + +### テスト結果 + +``` +Running 10 tests using 3 workers +✓ 10 passed (18.8s) +``` +--- + +## テストコードのリファクタリング + +### 実装内容 + +1. **`goToHome()` ヘルパー関数** + - `page.goto('http://localhost:5174/')` をヘルパー化 + - URL が変更されても一箇所の修正で済む(DRY 原則) + +2. **カスタムフィクスチャ実装(dark-mode.spec.ts)** + - `test.extend<{ iPhonePage: Page; desktopPage: Page }>()` で device 固有設定を抽象化 + - `iPhonePage`, `desktopPage` フィクスチャは `browser.newContext()` を自動管理 + - `await use(page)` 後に自動的に `context.close()` 実行 + +### 実装理由 + +- **DRY 原則**: domain knowledge(ホーム URL)を集約 +- **自動ライフサイクル管理**: 明示的に `browser.newContext()` を呼び出すテストでは context.close() が必須。Playwright の `await use()` パターンで確実なリソース解放を実現 +- **Playwright best practice**: + - 標準 `page` フィクスチャは自動クローズ(navbar.spec.ts のような単純なケース向け) + - device 固有設定が必要な場合のみ `test.extend()` で fixture 拡張(dark-mode.spec.ts) + - 型定義追加で TypeScript 検証を強化 + +--- + +### 重要な教訓 + +1. **Playwright API の変化** + - `page.setViewport()` は v2+ では削除 → `page.setViewportSize()` を使用 + - BDD スタイルテストでは `let page` の手動管理ではなく、`async ({ page })` で injection + - mobile/desktop テストは `devices` config を使用した `browser.newContext()` が推奨 + +2. **CSS ファイル構造の理解** + - SvelteKit ビルドは複数の CSS ファイルに分割される + - ビルド出力の `.svelte-kit/output/client/_app/immutable/assets/0.*.css` がメイン CSS + - CSS は圧縮されるため regex での検索はセレクタサイズの違いに注意(`\.bg-atcoder-` ではなく `bg-atcoder` で確認) + +3. **セレクタ設計の工夫** + - `aria-label` がない button を `nav button:not([aria-label])` で識別 + - `div[role="none"] ul` で menu container を特定(CSS class の `hidden`/`lg:block` に依存しない) + - Playwright の `isVisible()` は実際のレンダリング結果を確認(CSS 実装詳細に依存しない) + +4. **テストの安定性向上** + - viewport 変更後のページ再レンダリング問題 → `browser.newContext()` で新規コンテキスト作成 + - boundingBox 取得前に `toBeVisible()` で存在確認 + - 複数 SVG がある場合は `.locator('svg').count()` で存在確認 + +5. **カスタムフィクスチャの活用** + - `test.extend()` で device 固有設定を抽象化(`iPhonePage`, `desktopPage`) + - Playwright の `await use(page)` パターンで自動 context クローズを実現し、ボイラープレート削減 + - 型定義 `<{ iPhonePage: Page; desktopPage: Page }>` で TypeScript サポート確保 + +### 次フェーズへの道筋 + +- TailwindCSS v3 環境でカスタム colors と responsive が正常動作 ✅ +- E2E テストの基盤整備完了 → フェーズ0(TailwindCSS v4 移行)開始可能 +- 既存テストは v4 移行後の回帰検出に活用 + +--- + +**作成日:** 2026-01-02 +**最終更新:** 2026-01-02 +**ステータス:** フェーズ-1 完了(テストコード実装・全テスト PASS) diff --git a/docs/dev-notes/2026-01-02/migrate-from-svelte-5-ui-lib-to-flowbite-svelte/smoke-tests.md b/docs/dev-notes/2026-01-02/migrate-from-svelte-5-ui-lib-to-flowbite-svelte/smoke-tests.md new file mode 100644 index 000000000..65bb6a9be --- /dev/null +++ b/docs/dev-notes/2026-01-02/migrate-from-svelte-5-ui-lib-to-flowbite-svelte/smoke-tests.md @@ -0,0 +1,465 @@ +# スモークテスト実装ガイド + +svelte-5-ui-lib → flowbite-svelte 移行時の回帰検出テスト + +--- + +## 目次 + +1. [スモークテストの考え方](#スモークテストの考え方) +2. [根幹アプローチ](#根幹アプローチ) +3. [前提条件確認(Step 1)](#前提条件確認step-1) +4. [実装対象テスト](#実装対象テスト) +5. [テスト実行コマンド](#テスト実行コマンド) +6. [参考資料](#参考資料) + +--- + +## スモークテストの考え方 + +### 「スモークテスト」とは + +新しい実装が **「全体として壊れていないか」の最小限の確認テスト** です。 + +**このプロジェクトでの位置付け:** + +| 項目 | 詳細 | +| ------------ | ----------------------------------------------------------------------------------------------------------------------------- | +| **目的** | TailwindCSS v4 移行後と Flowbite Svelte 置換後に、前回の v3→v4 失敗(カラー未反映、レスポンシブ破損、ダークモード消失)を検出 | +| **範囲** | ビルド出力確認 + E2E ユーザー操作フロー | +| **時期** | 各フェーズ完了直後に実施 | +| **量** | フェーズあたり 1~2テストケース(最小限) | +| **実行時間** | 分単位(遅い) | + +### なぜスモークテストなのか + +- **早期発見**: ビジネス要件に直結した最小フロー検証で、大きな問題を素早く検出 +- **低コスト**: 詳細テストより準備が簡単(コンポーネント単位の複雑な props 確認不要) +- **リスク検出**: 前回の失敗ポイント(カラー、レスポンシブ、ダークモード)を重点化 + +--- + +## 根幹アプローチ + +### テストフレームワークの選択 + +| テスト種別 | Vitest | Playwright | +| ---------------------- | ------------------------ | ---------------------------- | +| **対象** | コンポーネント単体、関数 | ユーザーフロー(実ブラウザ) | +| **実行速度** | 高速(秒単位) | 遅い(分単位) | +| **デバッグ難度** | 低い | 高い | +| **スモークテスト向き** | △ 条件付き | ⭐ 推奨 | + +**このプロジェクトでは Playwright を重視:** + +- CSS ビルド出力検証(ファイル読み込み) +- 実ブラウザでの UI 動作確認(セレクタ特定、レイアウト確認) +- 前回失敗ポイント(レスポンシブ、ダークモード toggle)は UI 操作テストが有効 + +### フェーズごとのテスト実行タイミング + +| フェーズ | テスト | 目的 | +| ----------------------- | -------------------------------------------------- | ----------------------------------------------------- | +| **0**(TailwindCSS v4) | `tests/custom-colors.spec.ts` | ビルド出力確認:カスタムカラーが CSS に含まれているか | +| **2-1**(navbar 置換) | `tests/navbar.spec.ts` + `tests/dark-mode.spec.ts` | UI 動作確認:レスポンシブ、ダークモード | + +--- + +## 前提条件確認(Step 1) + +フェーズ実装前に、以下を手動確認してセレクタを特定します。 + +### 1-1. ダークモード切り替えボタンのセレクタ確認 + +**実装結果:** + +```html + + +``` + +**確定セレクタ:** + +- `button[aria-label="Dark mode"]` ✅ + +### 1-2. navbar 構造確認 + +**実装結果:** + +```html + + +``` + +**確定セレクタ:** + +- navbar: `nav` ✅ +- menu items: `nav ul li a` ✅ +- hamburger button: `nav button:not([aria-label])` ✅ +- menu container: `div[role="none"] ul` ✅ + +### 1-3. ブレークポイント確認 + +**確認結果:** + +| デバイス | 幅 | navbar menu 表示状態 | +| --------------- | ------ | -------------------------------------- | +| Mobile (iPhone) | 375px | `hidden` クラス + `lg:hidden` で非表示 | +| Tablet | 768px | 同上 | +| Desktop (lg) | 1024px | `lg:block` で表示 | + +**確認方法:** Playwright `isVisible()` で実際のレンダリング確認 + +--- + +## 実装対象テスト + +### テスト A: TailwindCSS v4 ビルド出力確認 + +**ファイル:** `tests/custom-colors.spec.ts` + +**フェーズ:** 0(TailwindCSS v4 移行直後) + +**目的:** ビルド出力に カスタムカラーが含まれているか確認(前回 v3→v4 失敗の再現防止) + +```typescript +import { test, expect } from '@playwright/test'; +import { readFileSync } from 'fs'; +import { resolve } from 'path'; + +test.describe('TailwindCSS v4 configuration', () => { + /** + * ビルド出力(.svelte-kit/output/client/_app/immutable/assets/0.*.css)に + * カスタムカラーが生成されているか確認 + * + * 前提条件:pnpm build 実行済み + */ + + test('primary color is generated in CSS', () => { + // build output をチェック + // パス: .svelte-kit/output/client/_app/immutable/assets/ の ハッシュ付きCSSファイル + const cssDir = resolve('.svelte-kit/output/client/_app/immutable/assets'); + const cssFiles = require('fs') + .readdirSync(cssDir) + .filter((f: string) => f.startsWith('0.') && f.endsWith('.css')); + + expect(cssFiles.length).toBeGreaterThan(0); + + const cssPath = resolve(cssDir, cssFiles[0]); + const css = readFileSync(cssPath, 'utf-8'); + + // primary-* クラスが生成されているか + expect(css).toMatch(/\.text-primary-[0-9]/); + expect(css).toMatch(/\.bg-primary-[0-9]/); + }); + + test('atcoder color is generated', () => { + const cssDir = resolve('.svelte-kit/output/client/_app/immutable/assets'); + const cssFiles = require('fs') + .readdirSync(cssDir) + .filter((f: string) => f.startsWith('0.') && f.endsWith('.css')); + const cssPath = resolve(cssDir, cssFiles[0]); + const css = readFileSync(cssPath, 'utf-8'); + + // atcoder-* クラスが生成されているか + expect(css).toMatch(/\.bg-atcoder-/); + }); + + test('xs breakpoint is available (or custom breakpoints)', () => { + const cssDir = resolve('.svelte-kit/output/client/_app/immutable/assets'); + const cssFiles = require('fs') + .readdirSync(cssDir) + .filter((f: string) => f.startsWith('0.') && f.endsWith('.css')); + const cssPath = resolve(cssDir, cssFiles[0]); + const css = readFileSync(cssPath, 'utf-8'); + + // カスタムブレークポイント確認 + // 420px が生成されているかを確認(またはメディアクエリが存在するか) + // TODO: tailwind.config.ts で xs: 420px が有効か確認 + expect(css.length).toBeGreaterThan(10000); // CSS が正常に生成されている + }); +}); +``` + +**実行タイミング:** + +```bash +# フェーズ 0: TailwindCSS v4 @import/@config/@source 対応完了直後 +pnpm build +pnpm playwright test tests/custom-colors.spec.ts +``` + +**成功条件:** + +- ✅ primary color classes が CSS に含まれている +- ✅ atcoder color classes が CSS に含まれている +- ✅ ビルド出力が正常に生成されている + +--- + +### テスト B: ダークモード回帰検出 + +**ファイル:** `tests/dark-mode.spec.ts` + +**フェーズ:** 2-1(navbar/auth コンポーネント置換完了後) + +**目的:** ダークモード切り替えボタンが消失していないか確認(前回 v3→v4 失敗の再現防止) + +```typescript +import { test, expect, Page } from '@playwright/test'; + +test.describe('Dark mode - Regression from v3->v4 migration', () => { + /** + * 前提条件: + * - Step 1 で確認したセレクタを使用 + * - pnpm dev で開発サーバ起動済み + */ + + let page: Page; + + test.beforeEach(async ({ browser }) => { + page = await browser.newPage(); + }); + + test.afterEach(async () => { + await page.close(); + }); + + test('dark toggle button is visible', async () => { + await page.goto('http://localhost:5174/'); + + const darkToggle = page.locator('button[aria-label="Dark mode"]'); + await expect(darkToggle).toBeVisible(); + }); + + test('dark mode icon shows correctly on mobile', async () => { + await page.setViewport({ width: 375, height: 667 }); // iPhone + await page.goto('http://localhost:5174/'); + + const darkIcon = page.locator('button[aria-label="Dark mode"] svg'); + await expect(darkIcon).toBeVisible(); + + // 位置がずれていないか確認(viewport 外ではない) + const bbox = await darkIcon.boundingBox(); + expect(bbox?.x).toBeGreaterThan(0); + expect(bbox?.width).toBeGreaterThan(0); + }); + + test('dark mode icon shows correctly on lg', async () => { + await page.setViewport({ width: 1024, height: 768 }); // desktop + await page.goto('http://localhost:5174/'); + + const darkIcon = page.locator('button[aria-label="Dark mode"] svg'); + await expect(darkIcon).toBeVisible(); + }); + + test('dark mode toggle switches theme', async () => { + await page.goto('http://localhost:5174/'); + + const html = page.locator('html'); + const initialClass = await html.getAttribute('class'); + + // toggle button クリック + const darkToggle = page.locator('button[aria-label="Dark mode"]'); + await darkToggle.click(); + + // クラスが変更されたか確認(dark class が toggle される) + const afterClass = await html.getAttribute('class'); + expect(initialClass).not.toBe(afterClass); + }); +}); +``` + +**実行タイミング:** + +```bash +# フェーズ 2-1: navbar/auth コンポーネント置換完了直後 +pnpm dev # 別ターミナルで起動 +pnpm playwright test tests/dark-mode.spec.ts +``` + +**成功条件:** + +- ✅ ダークモード button が visible +- ✅ mobile / lg で icon が正しく表示 +- ✅ toggle で `` が変更される + +--- + +### テスト C: navbar レスポンシブ動作確認 + +**ファイル:** `tests/navbar.spec.ts` + +**フェーズ:** 2-1(navbar/auth コンポーネント置換完了後) + +**目的:** navbar がレスポンシブに動作するか確認(前回 v3→v4 失敗の再現防止) + +```typescript +import { test, expect, Page } from '@playwright/test'; + +test.describe('Navbar - Regression from v3->v4 migration', () => { + /** + * 前提条件: + * - Step 1 で確認した navbar セレクタ(通常 nav 要素) + * - pnpm dev で開発サーバ起動済み + */ + + let page: Page; + + test.beforeEach(async ({ browser }) => { + page = await browser.newPage(); + }); + + test.afterEach(async () => { + await page.close(); + }); + + test('navbar is visible on lg (1024px)', async () => { + await page.setViewport({ width: 1024, height: 768 }); + await page.goto('http://localhost:5174/'); + + const navbar = page.locator('nav'); + await expect(navbar).toBeVisible(); + }); + + test('navbar menu items align properly on lg', async () => { + await page.setViewport({ width: 1024, height: 768 }); + await page.goto('http://localhost:5174/'); + + const navItems = page.locator('nav ul li a'); + const count = await navItems.count(); + expect(count).toBeGreaterThan(0); + + // menu items の位置確認(レイアウト破損がないか) + for (let i = 0; i < Math.min(count, 3); i++) { + const item = navItems.nth(i); + const bbox = await item.boundingBox(); + expect(bbox?.width).toBeGreaterThan(0); + expect(bbox?.height).toBeGreaterThan(0); + // navbar viewport 内に収まっているか + expect(bbox?.x || 0).toBeGreaterThanOrEqual(0); + } + }); + + test('navbar is visible and functional on mobile (375px)', async () => { + await page.setViewport({ width: 375, height: 667 }); + await page.goto('http://localhost:5174/'); + + const navbar = page.locator('nav'); + await expect(navbar).toBeVisible(); + + // mobile では hamburger menu が存在 + const hamburger = page.locator('nav button:not([aria-label])'); + await expect(hamburger).toBeVisible(); + + // menu が非表示(hidden class) + const menuContainer = page.locator('div[role="none"] ul'); + await expect(menuContainer).not.toBeVisible(); + }); +}); +``` + +**実行タイミング:** + +```bash +# フェーズ 2-1: navbar/auth コンポーネント置換完了直後 +pnpm dev # 別ターミナルで起動 +pnpm playwright test tests/navbar.spec.ts +``` + +**成功条件:** + +- ✅ navbar が lg(1024px)で visible +- ✅ menu items が複数存在し、レイアウトが正常 +- ✅ mobile(375px)で navbar が機能(hamburger または折り畳み表示) + +--- + +## テスト実行コマンド + +### Phase 0: TailwindCSS v4 ビルド確認 + +```bash +# 1. ビルド実行 +pnpm build + +# 2. カスタムカラー生成確認 +pnpm playwright test tests/custom-colors.spec.ts --ui +``` + +### Phase 2-1: navbar / dark mode 回帰テスト + +```bash +# 開発サーバ起動(別ターミナル) +pnpm dev + +# navbar テスト +pnpm playwright test tests/navbar.spec.ts --ui + +# ダークモード テスト +pnpm playwright test tests/dark-mode.spec.ts --ui + +# 両方実行 +pnpm playwright test tests/navbar.spec.ts tests/dark-mode.spec.ts --ui +``` + +### テスト実行オプション + +| オプション | 用途 | +| ----------------- | -------------------------------------------- | +| `--ui` | 対話モード(開発中推奨)- ブラウザで動作確認 | +| `--debug` | デバッグモード(ステップ実行) | +| `--headed` | 無視できるブラウザウィンドウを表示 | +| `--reporter=html` | HTML レポート生成 | + +### 全テスト実行(CI 用) + +```bash +pnpm build +pnpm playwright test +``` + +--- + +## セレクタ確認チェックリスト + +**2026-01-02 確認済み:** + +| 項目 | 確認状況 | セレクタ値 | +| --------------------------------- | ----------- | -------------------------------- | +| ダークモード button の aria-label | ✅ 確認済み | `button[aria-label="Dark mode"]` | +| navbar 要素 | ✅ 確認済み | `nav` | +| navbar menu items | ✅ 確認済み | `nav ul li a` | +| hamburger button (mobile) | ✅ 確認済み | `nav button:not([aria-label])` | +| menu container (visibility) | ✅ 確認済み | `div[role="none"] ul` | + +**確認完了 → テストコード実装開始** + +--- + +## 参考資料 + +- [Playwright 公式ドキュメント](https://playwright.dev/) +- [Playwright Inspector(デバッグ用)](https://playwright.dev/docs/inspector) +- [メイン計画ドキュメント](./plan.md) +- [コンポーネント対応マトリクス](./component-mapping.md) +- [前回失敗時のドキュメント](../2025-12-30/bump-tailwindcss-from-v3-to-v4/plan.md) + +--- + +**作成日:** 2026-01-02 +**ステータス:** ドラフト完成(テスト実装前) +**次ステップ:** Step 1 実行 → セレクタ確認 → テストコード実装 diff --git a/tests/custom-colors.spec.ts b/tests/custom-colors.spec.ts new file mode 100644 index 000000000..1044ecdda --- /dev/null +++ b/tests/custom-colors.spec.ts @@ -0,0 +1,75 @@ +import { test, expect } from '@playwright/test'; +import { readFileSync } from 'fs'; +import { resolve } from 'path'; +import { readdirSync } from 'fs'; + +test.describe('TailwindCSS v3 configuration', () => { + /** + * Verify that custom colors are generated in build output + * (.svelte-kit/output/client/_app/immutable/assets/0.*.css) + * + * Precondition: pnpm build executed + */ + + test('primary color is generated in CSS', () => { + // Validate build output + // Path: CSS files with hash in .svelte-kit/output/client/_app/immutable/assets/ + const cssDir = resolve('.svelte-kit/output/client/_app/immutable/assets'); + let cssFiles: string[] = []; + + try { + cssFiles = readdirSync(cssDir).filter( + (f: string) => f.startsWith('0.') && f.endsWith('.css'), + ); + } catch (e) { + // Fallback: search for all .css files + cssFiles = readdirSync(cssDir).filter((f: string) => f.endsWith('.css')); + } + + expect(cssFiles.length).toBeGreaterThan(0); + + const cssPath = resolve(cssDir, cssFiles[0]); + const css = readFileSync(cssPath, 'utf-8'); + + // Check if primary-* classes are generated + expect(css).toMatch(/\.text-primary-[0-9]/); + expect(css).toMatch(/\.bg-primary-[0-9]/); + }); + + test('atcoder color is generated', () => { + const cssDir = resolve('.svelte-kit/output/client/_app/immutable/assets'); + // Find 0.*.css file (main CSS file) + const mainCssFiles = readdirSync(cssDir) + .filter((f: string) => f.match(/^0\.[a-zA-Z0-9]+\.css$/)) + .sort() + .reverse(); + + expect(mainCssFiles.length).toBeGreaterThan(0); + + const cssPath = resolve(cssDir, mainCssFiles[0]); + const css = readFileSync(cssPath, 'utf-8'); + + // Validate if atcoder-* classes are generated + expect(css).toContain('bg-atcoder'); + }); + + test('xs breakpoint is available (or custom breakpoints)', () => { + const cssDir = resolve('.svelte-kit/output/client/_app/immutable/assets'); + const cssFiles = readdirSync(cssDir) + .filter((f: string) => f.endsWith('.css')) + .sort() + .reverse(); + + expect(cssFiles.length).toBeGreaterThan(0); + + // Verify total CSS size from multiple files + const totalCssSize = cssFiles.slice(0, 5).reduce((acc, f) => { + const cssPath = resolve(cssDir, f); + const css = readFileSync(cssPath, 'utf-8'); + return acc + css.length; + }, 0); + + // Verify CSS is generated properly (single file size may be small due to multiple files) + expect(totalCssSize).toBeGreaterThan(5000); + }); +}); diff --git a/tests/dark-mode.spec.ts b/tests/dark-mode.spec.ts new file mode 100644 index 000000000..9ac3791c4 --- /dev/null +++ b/tests/dark-mode.spec.ts @@ -0,0 +1,76 @@ +import { test as base, expect, devices, Page } from '@playwright/test'; + +// Mobile device config +const iPhone = devices['iPhone 12']; +const Desktop = { ...devices['Desktop Chrome'], viewport: { width: 1024, height: 768 } }; + +// Helper function to navigate to home +const goToHome = async (page: Page) => { + await page.goto('http://localhost:5174/'); +}; + +// Custom fixture for device-specific pages with automatic context cleanup +const test = base.extend<{ iPhonePage: Page; desktopPage: Page }>({ + iPhonePage: async ({ browser }, use) => { + const context = await browser.newContext(iPhone); + const page = await context.newPage(); + await use(page); + await context.close(); + }, + desktopPage: async ({ browser }, use) => { + const context = await browser.newContext(Desktop); + const page = await context.newPage(); + await use(page); + await context.close(); + }, +}); + +test.describe('Dark mode - Regression from v3->v4 migration', () => { + /** + * Preconditions: + * - Development server started with pnpm dev + */ + + test('dark toggle button is visible', async ({ page }) => { + await goToHome(page); + + const darkToggle = page.locator('button[aria-label="Dark mode"]'); + await expect(darkToggle).toBeVisible(); + }); + + test('dark mode button exists on mobile', async ({ iPhonePage }) => { + await goToHome(iPhonePage); + + // Verify button exists + const darkModeButton = iPhonePage.locator('button[aria-label="Dark mode"]'); + await expect(darkModeButton).toHaveCount(1); + }); + + test('dark mode button exists on lg', async ({ desktopPage }) => { + await goToHome(desktopPage); + + // Verify button exists + const darkModeButton = desktopPage.locator('button[aria-label="Dark mode"]'); + await expect(darkModeButton).toBeVisible(); + + // Verify SVG exists + const svgs = darkModeButton.locator('svg'); + const svgCount = await svgs.count(); + expect(svgCount).toBeGreaterThan(0); + }); + + test('dark mode toggle switches theme', async ({ page }) => { + await goToHome(page); + + const html = page.locator('html'); + const initialClass = await html.getAttribute('class'); + + // Click toggle button + const darkToggle = page.locator('button[aria-label="Dark mode"]'); + await darkToggle.click(); + + // Verify class changed (dark class toggled) + const afterClass = await html.getAttribute('class'); + expect(initialClass).not.toBe(afterClass); + }); +}); diff --git a/tests/navbar.spec.ts b/tests/navbar.spec.ts new file mode 100644 index 000000000..c0ddcbc72 --- /dev/null +++ b/tests/navbar.spec.ts @@ -0,0 +1,56 @@ +import { test, expect, Page } from '@playwright/test'; + +// Helper function to navigate to home +const goToHome = async (page: Page) => { + await page.goto('http://localhost:5174/'); +}; + +test.describe('Navbar - Regression from v3->v4 migration', () => { + /** + * Preconditions: + * - Development server started with pnpm dev + */ + + test('navbar is visible on lg (1024px)', async ({ page }) => { + await page.setViewportSize({ width: 1024, height: 768 }); + await goToHome(page); + + const navbar = page.locator('nav'); + await expect(navbar).toBeVisible(); + }); + + test('navbar menu items align properly on lg', async ({ page }) => { + await page.setViewportSize({ width: 1024, height: 768 }); + await goToHome(page); + + const navItems = page.locator('nav ul li a'); + const count = await navItems.count(); + expect(count).toBeGreaterThan(0); + + // Verify menu items position (check for layout corruption) + for (let i = 0; i < Math.min(count, 3); i++) { + const item = navItems.nth(i); + const bbox = await item.boundingBox(); + expect(bbox?.width).toBeGreaterThan(0); + expect(bbox?.height).toBeGreaterThan(0); + // Verify items fit within navbar viewport + expect(bbox?.x || 0).toBeGreaterThanOrEqual(0); + } + }); + + test('navbar is visible and functional on mobile (375px)', async ({ page }) => { + await page.setViewportSize({ width: 375, height: 667 }); + await goToHome(page); + + const navbar = page.locator('nav'); + await expect(navbar).toBeVisible(); + + // Verify hamburger menu exists on mobile + const hamburger = page.locator('nav button:not([aria-label])'); + await expect(hamburger).toBeVisible(); + + // Verify menu is hidden (hidden class) + const menuContainer = page.locator('div[role="none"] ul'); + await expect(menuContainer).not.toBeVisible(); + }); +}); From fe8e27ec563ddf56b99107c59ea6413df8d39406 Mon Sep 17 00:00:00 2001 From: Kato Hiroki Date: Sat, 3 Jan 2026 07:05:24 +0000 Subject: [PATCH 02/62] chore(e2e): Use baseURL (#2054) --- playwright.config.ts | 3 +++ tests/dark-mode.spec.ts | 2 +- tests/navbar.spec.ts | 2 +- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/playwright.config.ts b/playwright.config.ts index 9b6bb6e8d..5b7a63069 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -6,6 +6,9 @@ const config: PlaywrightTestConfig = { port: 4173, timeout: 10000 * 1000, }, + use: { + baseURL: process.env.BASE_URL ?? 'http://localhost:4173', + }, testDir: 'tests', projects: [ //{ diff --git a/tests/dark-mode.spec.ts b/tests/dark-mode.spec.ts index 9ac3791c4..666cc78ad 100644 --- a/tests/dark-mode.spec.ts +++ b/tests/dark-mode.spec.ts @@ -6,7 +6,7 @@ const Desktop = { ...devices['Desktop Chrome'], viewport: { width: 1024, height: // Helper function to navigate to home const goToHome = async (page: Page) => { - await page.goto('http://localhost:5174/'); + await page.goto('/'); }; // Custom fixture for device-specific pages with automatic context cleanup diff --git a/tests/navbar.spec.ts b/tests/navbar.spec.ts index c0ddcbc72..22a85f3b3 100644 --- a/tests/navbar.spec.ts +++ b/tests/navbar.spec.ts @@ -2,7 +2,7 @@ import { test, expect, Page } from '@playwright/test'; // Helper function to navigate to home const goToHome = async (page: Page) => { - await page.goto('http://localhost:5174/'); + await page.goto('/'); }; test.describe('Navbar - Regression from v3->v4 migration', () => { From 6f38ffbcb20cadb1a454a90d04880c1f72ee04d0 Mon Sep 17 00:00:00 2001 From: Kato Hiroki Date: Sat, 3 Jan 2026 07:06:14 +0000 Subject: [PATCH 03/62] chore(docs): Update plan (#2054) --- .../migrate-from-svelte-5-ui-lib-to-flowbite-svelte/plan.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/dev-notes/2026-01-02/migrate-from-svelte-5-ui-lib-to-flowbite-svelte/plan.md b/docs/dev-notes/2026-01-02/migrate-from-svelte-5-ui-lib-to-flowbite-svelte/plan.md index 64ced2ec8..83d12bd5f 100644 --- a/docs/dev-notes/2026-01-02/migrate-from-svelte-5-ui-lib-to-flowbite-svelte/plan.md +++ b/docs/dev-notes/2026-01-02/migrate-from-svelte-5-ui-lib-to-flowbite-svelte/plan.md @@ -510,6 +510,7 @@ pnpm playwright test tests/custom-colors.spec.ts Running 10 tests using 3 workers ✓ 10 passed (18.8s) ``` + --- ## テストコードのリファクタリング From 8ab297d49168ea9dab4974942e0428c6006492bb Mon Sep 17 00:00:00 2001 From: Kato Hiroki Date: Sat, 3 Jan 2026 07:10:33 +0000 Subject: [PATCH 04/62] chore: Fix format (#2054) --- tests/navbar.spec.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/navbar.spec.ts b/tests/navbar.spec.ts index 22a85f3b3..5a6395c07 100644 --- a/tests/navbar.spec.ts +++ b/tests/navbar.spec.ts @@ -31,6 +31,7 @@ test.describe('Navbar - Regression from v3->v4 migration', () => { for (let i = 0; i < Math.min(count, 3); i++) { const item = navItems.nth(i); const bbox = await item.boundingBox(); + expect(bbox?.width).toBeGreaterThan(0); expect(bbox?.height).toBeGreaterThan(0); // Verify items fit within navbar viewport From 0617ab7b2e3c7d5a1038ede259a5d5b1036b83f7 Mon Sep 17 00:00:00 2001 From: Kato Hiroki Date: Sat, 3 Jan 2026 07:12:04 +0000 Subject: [PATCH 05/62] chore(docs): Update plan (#2054) --- .../migrate-from-svelte-5-ui-lib-to-flowbite-svelte/plan.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/dev-notes/2026-01-02/migrate-from-svelte-5-ui-lib-to-flowbite-svelte/plan.md b/docs/dev-notes/2026-01-02/migrate-from-svelte-5-ui-lib-to-flowbite-svelte/plan.md index 83d12bd5f..35684fea9 100644 --- a/docs/dev-notes/2026-01-02/migrate-from-svelte-5-ui-lib-to-flowbite-svelte/plan.md +++ b/docs/dev-notes/2026-01-02/migrate-from-svelte-5-ui-lib-to-flowbite-svelte/plan.md @@ -80,6 +80,7 @@ - `tests/navbar.spec.ts` (新規) - `tests/custom-colors.spec.ts` (新規) +- `tests/dark-mode.spec.ts` (新規) **Gate チェック:** From baeccc0e51a1e376f49e98db72fcfb0212edab0b Mon Sep 17 00:00:00 2001 From: Kato Hiroki Date: Sat, 3 Jan 2026 09:34:14 +0000 Subject: [PATCH 06/62] chore(docs): Update plan (#2054) --- .../plan.md | 71 +++++++++++++++++++ 1 file changed, 71 insertions(+) diff --git a/docs/dev-notes/2026-01-02/migrate-from-svelte-5-ui-lib-to-flowbite-svelte/plan.md b/docs/dev-notes/2026-01-02/migrate-from-svelte-5-ui-lib-to-flowbite-svelte/plan.md index 35684fea9..5a84b3f9b 100644 --- a/docs/dev-notes/2026-01-02/migrate-from-svelte-5-ui-lib-to-flowbite-svelte/plan.md +++ b/docs/dev-notes/2026-01-02/migrate-from-svelte-5-ui-lib-to-flowbite-svelte/plan.md @@ -97,6 +97,24 @@ **目的:** Tailwind v4 環境を整え、カスタム colors と breakpoints が機能する状態にする +#### 0-0. Flowbite / Flowbite Svelte Breaking Changes 確認 + +**確認事項:** + +- [ ] Flowbite v3.0.0 以降の CSS architecture 変更を理解(詳細は「[Flowbite Breaking Changes(詳細)](#flowbite-breaking-changes詳細)」参照) + - TailwindCSS v4 への統合が完了しており、flowbite-svelte v1.31.0 経由で対応済み + - 直接対応不要:CSS ライブラリとしての変更は Svelte レイヤーで抽象化される + +- [ ] Flowbite Svelte v0.45.0 以降 → v1.31.0 の breaking changes を把握 + - [Node 要件変更](https://github.com/themesberg/flowbite-svelte/releases/tag/v0.45.0)(>= 20.0.0) + - フェーズ1 でコンポーネント置き換え時に個別確認 + +**出力:** 既存テスト環境(フェーズ-1 で実装)が v1.31.0 と互換性を持つことを確認 + +**工数:** < 1 day(文献レビュー) + +--- + #### 0-1. TailwindCSS v4 breaking changes(必須対応) 以下の **必須** 変更を実装: @@ -391,6 +409,59 @@ pnpm test:integration --- +## Flowbite Breaking Changes(詳細) + +### Flowbite v2.5.0 → v3.1.2 の破壊的変更 + +#### v3.0.0 (2025-01-24) - TailwindCSS v4 統合による大型変更 + +**主要な破壊的変更:** + +- **TailwindCSS v4 への完全移行** + - [GitHub Release](https://github.com/themesberg/flowbite/releases/tag/v3.0.0) + - CSS architecture が完全に再設計 + - CSS variables の生成方式が変更 + - Plugin システムが新規実装 + +**対象プロジェクトへの影響:** + +- ✅ **直接影響なし** - Flowbite は CSS ライブラリであり、コンポーネント構造に変更なし +- ✅ **flowbite-svelte v1.31.0 が対応済み** - Svelte レイヤーで抽象化 + +#### v3.0.0 ~ v3.1.2 の間 + +**v3.1.0, v3.1.1, v3.1.2:** + +- [CSS variables のバグ修正](https://github.com/themesberg/flowbite/releases/tag/v3.1.2)(theme file 新規作成) +- 新たな破壊的変更なし - v3.0.0 が最大の転換点 + +--- + +### Flowbite Svelte v0.45.0 以降 → v1.31.0 の破壊的変更 + +#### v0.45.0 (2024-04-16) - Node 要件変更 + +**破壊的変更:** + +- **Node 要件を >= 20.0.0 に引き上げ** + - [GitHub Release](https://github.com/themesberg/flowbite-svelte/releases/tag/v0.45.0) + - v0.44 までは Node >= 18.0.0 で動作 + +**対象プロジェクトへの影響:** + +- 🟡 **Node 要件確認が必須** - 本プロジェクトが Node 20+ で動作していることを確認 + - [参考: package.json engines 確認](../../../../../../package.json)(✅ 既に Node >= 20.0.0 設定済み) + +#### v0.45.0 ~ v1.31.0 の間 + +**v0.47.0 ~ v1.0.0 ~ v1.31.0:** + +- 機能追加と軽微なバグ修正のみ +- [v1.0.0 では Button cursor デフォルト修正](https://github.com/themesberg/flowbite-svelte/releases/tag/v1.0.0)(TailwindCSS v4.0.0 対応) +- 新たな破壊的変更なし + +--- + ## TailwindCSS v4 Breaking Changes(詳細) ### 必須変更 From 5f4c0cc165fe8684b44eea4a945c7ac66a55c521 Mon Sep 17 00:00:00 2001 From: Kato Hiroki Date: Sat, 3 Jan 2026 13:27:12 +0000 Subject: [PATCH 07/62] breaking: Bump tailwindcss from v3 to v4, bump flowbite from v2 to v3 and add flowbite-svelte (#2054) --- .../plan.md | 84 ++- package.json | 8 +- pnpm-lock.yaml | 517 ++++++++++++++++-- postcss.config.mjs | 12 +- src/app.css | 10 +- tailwind.config.ts | 5 - 6 files changed, 558 insertions(+), 78 deletions(-) diff --git a/docs/dev-notes/2026-01-02/migrate-from-svelte-5-ui-lib-to-flowbite-svelte/plan.md b/docs/dev-notes/2026-01-02/migrate-from-svelte-5-ui-lib-to-flowbite-svelte/plan.md index 5a84b3f9b..07889f500 100644 --- a/docs/dev-notes/2026-01-02/migrate-from-svelte-5-ui-lib-to-flowbite-svelte/plan.md +++ b/docs/dev-notes/2026-01-02/migrate-from-svelte-5-ui-lib-to-flowbite-svelte/plan.md @@ -101,17 +101,22 @@ **確認事項:** -- [ ] Flowbite v3.0.0 以降の CSS architecture 変更を理解(詳細は「[Flowbite Breaking Changes(詳細)](#flowbite-breaking-changes詳細)」参照) +- [x] Flowbite v3.0.0 以降の CSS architecture 変更を理解(詳細は「[Flowbite Breaking Changes(詳細)](#flowbite-breaking-changes詳細)」参照) - TailwindCSS v4 への統合が完了しており、flowbite-svelte v1.31.0 経由で対応済み - 直接対応不要:CSS ライブラリとしての変更は Svelte レイヤーで抽象化される -- [ ] Flowbite Svelte v0.45.0 以降 → v1.31.0 の breaking changes を把握 +- [x] Flowbite Svelte v0.45.0 以降 → v1.31.0 の breaking changes を把握 - [Node 要件変更](https://github.com/themesberg/flowbite-svelte/releases/tag/v0.45.0)(>= 20.0.0) - フェーズ1 でコンポーネント置き換え時に個別確認 + - **環境確認**: Node v22.21.1 ✅、pnpm 10.26.2 ✅ -**出力:** 既存テスト環境(フェーズ-1 で実装)が v1.31.0 と互換性を持つことを確認 +- [x] Flowbite v2.5.2 → v3.1.2 へのアップグレード ✅ +- [x] flowbite-svelte v1.31.0 のインストール(新規) ✅ +- [x] flowbite-svelte-icons は `@lucide/svelte` を使用するためスキップ -**工数:** < 1 day(文献レビュー) +**出力:** Flowbite v3.1.2 + flowbite-svelte v1.31.0 がインストール済み + +**工数:** < 1 day(文献レビュー + インストール) ✅ --- @@ -203,9 +208,8 @@ export default { pnpm build # CSS が生成されたか確認 -cat dist/bundle.css | grep "\.text-primary-500" -cat dist/bundle.css | grep "\.bg-atcoder" -cat dist/bundle.css | grep "@media (max-width: 420px)" +cat .svelte-kit/output/client/_app/immutable/assets/*.css | grep -E "(text-primary-500|bg-atcoder)" | head -3 +cat .svelte-kit/output/client/_app/immutable/assets/*.css | grep "@media.*420px" | head -3 ``` **確認項目:** @@ -645,5 +649,67 @@ Running 10 tests using 3 workers --- **作成日:** 2026-01-02 -**最終更新:** 2026-01-02 -**ステータス:** フェーズ-1 完了(テストコード実装・全テスト PASS) +**最終更新:** 2026-01-03 +**ステータス:** フェーズ0 完了(TailwindCSS v3→v4 移行完了、ビルド成功) + +--- + +## フェーズ0 実装結果と教訓(2026-01-03) + +### 実装内容 + +✅ **TailwindCSS v4 への移行を完了** + +1. **src/app.css の v4 記法への更新** + - `@tailwind base/components/utilities` → `@import 'tailwindcss'` + `@plugin` + `@custom-variant dark` に統一 + - `@source` ディレクティブでコンテンツスキャン範囲を明示 + +2. **tailwind.config.ts の簡潔化** + - `content: [...]` オプション削除(v4 では CSS の `@source` で指定) + - plugins, theme.extend は維持 + +3. **postcss.config.mjs の v4 対応** + - `postcss-tailwindcss` から `@tailwindcss/postcss` プラグインに変更 + +4. **依存関係の更新** + - TailwindCSS: 3.4.19 → 4.1.18 + - @tailwindcss/postcss: 新規インストール (4.1.18) + +5. **ビルド成功** + - `pnpm build` 実行成功(0 errors, 7.64s) + - .svelte-kit 出力ファイル生成確認 ✅ + +### 重要な教訓 + +1. **v4 の @source ディレクティブの必須性** + - TailwindCSS v4 では `@source` で指定したディレクトリのファイルのみスキャン + - flowbite-svelte コンポーネント利用時は `@source '../node_modules/flowbite-svelte/dist'` 等を明示する必要がある + - テストで CSS ファイルの存在だけを確認するのではなく、実際のコンポーネント使用状況を見る方が信頼性が高い + +2. **v3 → v4 移行の段階的アプローチ** + - 必須変更(CSS ディレクティブ、@source、plugin 指定)だけを先行実装 + - 任意推奨事項(CSS Variables 化、autoprefixer 削除など)はフェーズ1 以降で検討 + - この段階的アプローチにより、安定した状態での次フェーズ開始が可能 + +3. **テスト構成の課題** + - v4 では「colors が CSS に含まれるか」を単純に grep で検証することは困難 + - 実際の HTML 内で使用されているクラス名のみ生成されるため、Playwright での E2E テストが有効(実際のレンダリング結果を確認) + - Unit テストは「config が正しく読み込まれているか」レベルで十分 + +4. **devDependencies vs dependencies の確認** + - TailwindCSS は devDependency(ビルド時のみ必要) + - @tailwindcss/postcss は devDependency + - 本番環境では不要なため、`pnpm build` での最適化は期待通り + +### 次フェーズへの準備状態 + +- ✅ ビルド環境が v4 で動作確認済み +- ✅ postcss、tailwind.config が正常に機能 +- ✅ flowbite plugin が組み込まれている +- ✅ フェーズ1(UI ライブラリ置き換え)開始可能 + +--- + +**作成日:** 2026-01-02 +**最終更新:** 2026-01-03 +**ステータス:** フェーズ0 完了 diff --git a/package.json b/package.json index 08493726a..609138942 100644 --- a/package.json +++ b/package.json @@ -31,6 +31,7 @@ "@sveltejs/kit": "2.43.8", "@sveltejs/vite-plugin-svelte": "6.2.1", "@tailwindcss/forms": "0.5.11", + "@tailwindcss/postcss": "4.1.18", "@testing-library/jest-dom": "6.9.1", "@types/gtag.js": "0.0.20", "@types/jsdom": "27.0.0", @@ -41,9 +42,11 @@ "eslint": "9.39.2", "eslint-config-prettier": "10.1.8", "eslint-plugin-svelte": "3.10.1", + "flowbite": "3.1.2", + "flowbite-svelte": "1.31.0", "globals": "17.0.0", - "lefthook": "2.0.13", "jsdom": "27.4.0", + "lefthook": "2.0.13", "nock": "14.0.10", "pnpm": "10.26.2", "prettier": "3.7.4", @@ -56,6 +59,7 @@ "svelte-check": "4.3.5", "svelte-meta-tags": "4.5.0", "sveltekit-superforms": "2.27.4", + "tailwindcss": "4.1.18", "tslib": "2.8.1", "tsx": "4.21.0", "typescript": "5.9.3", @@ -77,14 +81,12 @@ "debug": "4.4.3", "embla-carousel-autoplay": "8.6.0", "embla-carousel-svelte": "8.6.0", - "flowbite": "2.5.2", "lucia": "2.7.7", "p-queue": "^9.0.1", "playwright": "1.57.0", "prisma-erd-generator": "2.4.2", "svelte-eslint-parser": "1.4.1", "tailwind-merge": "2.6.0", - "tailwindcss": "3.4.19", "vercel": "50.1.3", "xss": "1.0.15" }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3552b9e7c..565de5e12 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -25,7 +25,7 @@ importers: version: 5.22.0(prisma@5.22.0) '@testing-library/svelte': specifier: 5.3.1 - version: 5.3.1(svelte@5.46.1)(vite@7.3.0(@types/node@25.0.3)(jiti@1.21.7)(tsx@4.21.0)(yaml@2.8.1))(vitest@4.0.16) + version: 5.3.1(svelte@5.46.1)(vite@7.3.0(@types/node@25.0.3)(jiti@1.21.7)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.1))(vitest@4.0.16) '@types/jest': specifier: 30.0.0 version: 30.0.0 @@ -44,9 +44,6 @@ importers: embla-carousel-svelte: specifier: 8.6.0 version: 8.6.0(svelte@5.46.1) - flowbite: - specifier: 2.5.2 - version: 2.5.2(rollup@4.53.4) lucia: specifier: 2.7.7 version: 2.7.7 @@ -65,9 +62,6 @@ importers: tailwind-merge: specifier: 2.6.0 version: 2.6.0 - tailwindcss: - specifier: 3.4.19 - version: 3.4.19(tsx@4.21.0)(yaml@2.8.1) vercel: specifier: 50.1.3 version: 50.1.3(rollup@4.53.4)(typescript@5.9.3) @@ -89,16 +83,19 @@ importers: version: 2.3.3(@prisma/client@5.22.0(prisma@5.22.0))(magicast@0.3.5)(typescript@5.9.3) '@sveltejs/adapter-vercel': specifier: 6.2.0 - version: 6.2.0(@sveltejs/kit@2.43.8(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.46.1)(vite@7.3.0(@types/node@25.0.3)(jiti@1.21.7)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.46.1)(vite@7.3.0(@types/node@25.0.3)(jiti@1.21.7)(tsx@4.21.0)(yaml@2.8.1)))(rollup@4.53.4) + version: 6.2.0(@sveltejs/kit@2.43.8(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.46.1)(vite@7.3.0(@types/node@25.0.3)(jiti@1.21.7)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.46.1)(vite@7.3.0(@types/node@25.0.3)(jiti@1.21.7)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.1)))(rollup@4.53.4) '@sveltejs/kit': specifier: 2.43.8 - version: 2.43.8(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.46.1)(vite@7.3.0(@types/node@25.0.3)(jiti@1.21.7)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.46.1)(vite@7.3.0(@types/node@25.0.3)(jiti@1.21.7)(tsx@4.21.0)(yaml@2.8.1)) + version: 2.43.8(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.46.1)(vite@7.3.0(@types/node@25.0.3)(jiti@1.21.7)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.46.1)(vite@7.3.0(@types/node@25.0.3)(jiti@1.21.7)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.1)) '@sveltejs/vite-plugin-svelte': specifier: 6.2.1 - version: 6.2.1(svelte@5.46.1)(vite@7.3.0(@types/node@25.0.3)(jiti@1.21.7)(tsx@4.21.0)(yaml@2.8.1)) + version: 6.2.1(svelte@5.46.1)(vite@7.3.0(@types/node@25.0.3)(jiti@1.21.7)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.1)) '@tailwindcss/forms': specifier: 0.5.11 - version: 0.5.11(tailwindcss@3.4.19(tsx@4.21.0)(yaml@2.8.1)) + version: 0.5.11(tailwindcss@4.1.18) + '@tailwindcss/postcss': + specifier: 4.1.18 + version: 4.1.18 '@testing-library/jest-dom': specifier: 6.9.1 version: 6.9.1 @@ -129,6 +126,12 @@ importers: eslint-plugin-svelte: specifier: 3.10.1 version: 3.10.1(eslint@9.39.2(jiti@1.21.7))(svelte@5.46.1)(ts-node@10.9.1(@types/node@25.0.3)(typescript@5.9.3)) + flowbite: + specifier: 3.1.2 + version: 3.1.2(rollup@4.53.4) + flowbite-svelte: + specifier: 1.31.0 + version: 1.31.0(rollup@4.53.4)(svelte@5.46.1)(tailwindcss@4.1.18) globals: specifier: 17.0.0 version: 17.0.0 @@ -164,7 +167,7 @@ importers: version: 5.46.1 svelte-5-ui-lib: specifier: 0.12.2 - version: 0.12.2(svelte@5.46.1)(tailwindcss@3.4.19(tsx@4.21.0)(yaml@2.8.1)) + version: 0.12.2(svelte@5.46.1)(tailwindcss@4.1.18) svelte-check: specifier: 4.3.5 version: 4.3.5(picomatch@4.0.3)(svelte@5.46.1)(typescript@5.9.3) @@ -173,7 +176,10 @@ importers: version: 4.5.0(svelte@5.46.1) sveltekit-superforms: specifier: 2.27.4 - version: 2.27.4(@sveltejs/kit@2.43.8(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.46.1)(vite@7.3.0(@types/node@25.0.3)(jiti@1.21.7)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.46.1)(vite@7.3.0(@types/node@25.0.3)(jiti@1.21.7)(tsx@4.21.0)(yaml@2.8.1)))(@types/json-schema@7.0.15)(esbuild@0.27.1)(svelte@5.46.1)(typescript@5.9.3) + version: 2.27.4(@sveltejs/kit@2.43.8(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.46.1)(vite@7.3.0(@types/node@25.0.3)(jiti@1.21.7)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.46.1)(vite@7.3.0(@types/node@25.0.3)(jiti@1.21.7)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.1)))(@types/json-schema@7.0.15)(esbuild@0.27.1)(svelte@5.46.1)(typescript@5.9.3) + tailwindcss: + specifier: 4.1.18 + version: 4.1.18 tslib: specifier: 2.8.1 version: 2.8.1 @@ -185,10 +191,10 @@ importers: version: 5.9.3 vite: specifier: 7.3.0 - version: 7.3.0(@types/node@25.0.3)(jiti@1.21.7)(tsx@4.21.0)(yaml@2.8.1) + version: 7.3.0(@types/node@25.0.3)(jiti@1.21.7)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.1) vitest: specifier: 4.0.16 - version: 4.0.16(@edge-runtime/vm@3.2.0)(@types/node@25.0.3)(@vitest/ui@4.0.16)(jiti@1.21.7)(jsdom@27.4.0)(tsx@4.21.0)(yaml@2.8.1) + version: 4.0.16(@edge-runtime/vm@3.2.0)(@types/node@25.0.3)(@vitest/ui@4.0.16)(jiti@1.21.7)(jsdom@27.4.0)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.1) zod: specifier: 3.25.76 version: 3.25.76 @@ -1708,6 +1714,31 @@ packages: svelte: ^5.0.0 vite: ^6.3.0 || ^7.0.0 + '@svgdotjs/svg.draggable.js@3.0.6': + resolution: {integrity: sha512-7iJFm9lL3C40HQcqzEfezK2l+dW2CpoVY3b77KQGqc8GXWa6LhhmX5Ckv7alQfUXBuZbjpICZ+Dvq1czlGx7gA==} + peerDependencies: + '@svgdotjs/svg.js': ^3.2.4 + + '@svgdotjs/svg.filter.js@3.0.9': + resolution: {integrity: sha512-/69XMRCDoam2HgC4ldHIaDgeQf1ViHIsa0Ld4uWgiXtZ+E24DWHe/9Ib6kbNiZ7WRIdlVokUDR1Fg0kjIpkfbw==} + engines: {node: '>= 0.8.0'} + + '@svgdotjs/svg.js@3.2.5': + resolution: {integrity: sha512-/VNHWYhNu+BS7ktbYoVGrCmsXDh+chFMaONMwGNdIBcFHrWqk2jY8fNyr3DLdtQUIalvkPfM554ZSFa3dm3nxQ==} + + '@svgdotjs/svg.resize.js@2.0.5': + resolution: {integrity: sha512-4heRW4B1QrJeENfi7326lUPYBCevj78FJs8kfeDxn5st0IYPIRXoTtOSYvTzFWgaWWXd3YCDE6ao4fmv91RthA==} + engines: {node: '>= 14.18'} + peerDependencies: + '@svgdotjs/svg.js': ^3.2.4 + '@svgdotjs/svg.select.js': ^4.0.1 + + '@svgdotjs/svg.select.js@4.0.3': + resolution: {integrity: sha512-qkMgso1sd2hXKd1FZ1weO7ANq12sNmQJeGDjs46QwDVsxSRcHmvWKL2NDF7Yimpwf3sl5esOLkPqtV2bQ3v/Jg==} + engines: {node: '>= 14.18'} + peerDependencies: + '@svgdotjs/svg.js': ^3.2.4 + '@swc/helpers@0.5.17': resolution: {integrity: sha512-5IKx/Y13RsYd+sauPb2x+U/xZikHjolzfuDgTAl/Tdf3Q8rslRvC19NKDLgAJQ6wsqADk10ntlv08nPFw/gO/A==} @@ -1716,6 +1747,94 @@ packages: peerDependencies: tailwindcss: '>=3.0.0 || >= 3.0.0-alpha.1 || >= 4.0.0-alpha.20 || >= 4.0.0-beta.1' + '@tailwindcss/node@4.1.18': + resolution: {integrity: sha512-DoR7U1P7iYhw16qJ49fgXUlry1t4CpXeErJHnQ44JgTSKMaZUdf17cfn5mHchfJ4KRBZRFA/Coo+MUF5+gOaCQ==} + + '@tailwindcss/oxide-android-arm64@4.1.18': + resolution: {integrity: sha512-dJHz7+Ugr9U/diKJA0W6N/6/cjI+ZTAoxPf9Iz9BFRF2GzEX8IvXxFIi/dZBloVJX/MZGvRuFA9rqwdiIEZQ0Q==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [android] + + '@tailwindcss/oxide-darwin-arm64@4.1.18': + resolution: {integrity: sha512-Gc2q4Qhs660bhjyBSKgq6BYvwDz4G+BuyJ5H1xfhmDR3D8HnHCmT/BSkvSL0vQLy/nkMLY20PQ2OoYMO15Jd0A==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [darwin] + + '@tailwindcss/oxide-darwin-x64@4.1.18': + resolution: {integrity: sha512-FL5oxr2xQsFrc3X9o1fjHKBYBMD1QZNyc1Xzw/h5Qu4XnEBi3dZn96HcHm41c/euGV+GRiXFfh2hUCyKi/e+yw==} + engines: {node: '>= 10'} + cpu: [x64] + os: [darwin] + + '@tailwindcss/oxide-freebsd-x64@4.1.18': + resolution: {integrity: sha512-Fj+RHgu5bDodmV1dM9yAxlfJwkkWvLiRjbhuO2LEtwtlYlBgiAT4x/j5wQr1tC3SANAgD+0YcmWVrj8R9trVMA==} + engines: {node: '>= 10'} + cpu: [x64] + os: [freebsd] + + '@tailwindcss/oxide-linux-arm-gnueabihf@4.1.18': + resolution: {integrity: sha512-Fp+Wzk/Ws4dZn+LV2Nqx3IilnhH51YZoRaYHQsVq3RQvEl+71VGKFpkfHrLM/Li+kt5c0DJe/bHXK1eHgDmdiA==} + engines: {node: '>= 10'} + cpu: [arm] + os: [linux] + + '@tailwindcss/oxide-linux-arm64-gnu@4.1.18': + resolution: {integrity: sha512-S0n3jboLysNbh55Vrt7pk9wgpyTTPD0fdQeh7wQfMqLPM/Hrxi+dVsLsPrycQjGKEQk85Kgbx+6+QnYNiHalnw==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + + '@tailwindcss/oxide-linux-arm64-musl@4.1.18': + resolution: {integrity: sha512-1px92582HkPQlaaCkdRcio71p8bc8i/ap5807tPRDK/uw953cauQBT8c5tVGkOwrHMfc2Yh6UuxaH4vtTjGvHg==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + + '@tailwindcss/oxide-linux-x64-gnu@4.1.18': + resolution: {integrity: sha512-v3gyT0ivkfBLoZGF9LyHmts0Isc8jHZyVcbzio6Wpzifg/+5ZJpDiRiUhDLkcr7f/r38SWNe7ucxmGW3j3Kb/g==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + + '@tailwindcss/oxide-linux-x64-musl@4.1.18': + resolution: {integrity: sha512-bhJ2y2OQNlcRwwgOAGMY0xTFStt4/wyU6pvI6LSuZpRgKQwxTec0/3Scu91O8ir7qCR3AuepQKLU/kX99FouqQ==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + + '@tailwindcss/oxide-wasm32-wasi@4.1.18': + resolution: {integrity: sha512-LffYTvPjODiP6PT16oNeUQJzNVyJl1cjIebq/rWWBF+3eDst5JGEFSc5cWxyRCJ0Mxl+KyIkqRxk1XPEs9x8TA==} + engines: {node: '>=14.0.0'} + cpu: [wasm32] + bundledDependencies: + - '@napi-rs/wasm-runtime' + - '@emnapi/core' + - '@emnapi/runtime' + - '@tybys/wasm-util' + - '@emnapi/wasi-threads' + - tslib + + '@tailwindcss/oxide-win32-arm64-msvc@4.1.18': + resolution: {integrity: sha512-HjSA7mr9HmC8fu6bdsZvZ+dhjyGCLdotjVOgLA2vEqxEBZaQo9YTX4kwgEvPCpRh8o4uWc4J/wEoFzhEmjvPbA==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [win32] + + '@tailwindcss/oxide-win32-x64-msvc@4.1.18': + resolution: {integrity: sha512-bJWbyYpUlqamC8dpR7pfjA0I7vdF6t5VpUGMWRkXVE3AXgIZjYUYAK7II1GNaxR8J1SSrSrppRar8G++JekE3Q==} + engines: {node: '>= 10'} + cpu: [x64] + os: [win32] + + '@tailwindcss/oxide@4.1.18': + resolution: {integrity: sha512-EgCR5tTS5bUSKQgzeMClT6iCY3ToqE1y+ZB0AKldj809QXk1Y+3jB0upOYZrn9aGIzPtUsP7sX4QQ4XtjBB95A==} + engines: {node: '>= 10'} + + '@tailwindcss/postcss@4.1.18': + resolution: {integrity: sha512-Ce0GFnzAOuPyfV5SxjXGn0CubwGcuDB0zcdaPuCSzAa/2vII24JTkH+I6jcbXLb1ctjZMZZI6OjDaLPJQL1S0g==} + '@tanstack/react-virtual@3.13.12': resolution: {integrity: sha512-Gd13QdxPSukP8ZrkbgS2RwoZseTTbQPLnQEn7HY/rqtM+8Zt95f7xKC7N0EsKs7aoz0WzZ+fditZux+F8EzYxA==} peerDependencies: @@ -2238,6 +2357,9 @@ packages: apexcharts@3.54.1: resolution: {integrity: sha512-E4et0h/J1U3r3EwS/WlqJCQIbepKbp6wGUmaAwJOMjHUP4Ci0gxanLa7FR3okx6p9coi4st6J853/Cb1NP0vpA==} + apexcharts@5.3.6: + resolution: {integrity: sha512-sVEPw+J0Gp0IHQabKu8cfdsxlfME0e36Wid7RIaPclGM2OUt+O7O4+6mfAmTUYhy5bDk8cNHzEhPfVtLCIXEJA==} + arg@4.1.0: resolution: {integrity: sha512-ZWc51jO3qegGkVh8Hwpv636EkbesNV5ZNQPCtRa+0qytRYPEs9IYT9qITY9buezqUH5uqyzlWLcufrzU2rffdg==} @@ -2803,6 +2925,9 @@ packages: resolution: {integrity: sha512-BnBS08aLUM+DKamupXs3w2tJJoqU+AkaE/+6vQxi/G/DPmIZFJJp9Dkb1kM03AZx8ADehDUZgsNxju3mPXZYIA==} engines: {node: '>=20'} + date-fns@4.1.0: + resolution: {integrity: sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==} + dayjs@1.11.18: resolution: {integrity: sha512-zFBQ7WFRvVRhKcWoUh+ZA1g2HVgUbsZm9sbddh8EC5iv93sui8DVVz1Npvz+r6meo9VKfa8NyLWBsQK1VvIKPA==} @@ -2954,6 +3079,10 @@ packages: end-of-stream@1.4.5: resolution: {integrity: sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==} + enhanced-resolve@5.18.4: + resolution: {integrity: sha512-LgQMM4WXU3QI+SYgEc2liRgznaD5ojbmY3sb8LxyguVkIg5FxdpTkvk72te2R38/TGKxH634oLxXRGY6d7AP+Q==} + engines: {node: '>=10.13.0'} + entities@6.0.1: resolution: {integrity: sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==} engines: {node: '>=0.12'} @@ -3354,9 +3483,18 @@ packages: flowbite-datepicker@1.3.2: resolution: {integrity: sha512-6Nfm0MCVX3mpaR7YSCjmEO2GO8CDt6CX8ZpQnGdeu03WUCWtEPQ/uy0PUiNtIJjJZWnX0Cm3H55MOhbD1g+E/g==} + flowbite-svelte@1.31.0: + resolution: {integrity: sha512-A7Ts/R5GsL8DbgRf+8+1wdrIOOK0nq4ggEkv4RuY0oGuzH1PLBAH+bvC1L8AgQ5li9mj3o8eE9tHW7Md8yjPsw==} + peerDependencies: + svelte: ^5.40.0 + tailwindcss: ^4.1.4 + flowbite@2.5.2: resolution: {integrity: sha512-kwFD3n8/YW4EG8GlY3Od9IoKND97kitO+/ejISHSqpn3vw2i5K/+ZI8Jm2V+KC4fGdnfi0XZ+TzYqQb4Q1LshA==} + flowbite@3.1.2: + resolution: {integrity: sha512-MkwSgbbybCYgMC+go6Da5idEKUFfMqc/AmSjm/2ZbdmvoKf5frLPq/eIhXc9P+rC8t9boZtUXzHDgt5whZ6A/Q==} + form-data@4.0.5: resolution: {integrity: sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==} engines: {node: '>= 6'} @@ -3840,6 +3978,76 @@ packages: libphonenumber-js@1.12.25: resolution: {integrity: sha512-u90tUu/SEF8b+RaDKCoW7ZNFDakyBtFlX1ex3J+VH+ElWes/UaitJLt/w4jGu8uAE41lltV/s+kMVtywcMEg7g==} + lightningcss-android-arm64@1.30.2: + resolution: {integrity: sha512-BH9sEdOCahSgmkVhBLeU7Hc9DWeZ1Eb6wNS6Da8igvUwAe0sqROHddIlvU06q3WyXVEOYDZ6ykBZQnjTbmo4+A==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [android] + + lightningcss-darwin-arm64@1.30.2: + resolution: {integrity: sha512-ylTcDJBN3Hp21TdhRT5zBOIi73P6/W0qwvlFEk22fkdXchtNTOU4Qc37SkzV+EKYxLouZ6M4LG9NfZ1qkhhBWA==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [darwin] + + lightningcss-darwin-x64@1.30.2: + resolution: {integrity: sha512-oBZgKchomuDYxr7ilwLcyms6BCyLn0z8J0+ZZmfpjwg9fRVZIR5/GMXd7r9RH94iDhld3UmSjBM6nXWM2TfZTQ==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [darwin] + + lightningcss-freebsd-x64@1.30.2: + resolution: {integrity: sha512-c2bH6xTrf4BDpK8MoGG4Bd6zAMZDAXS569UxCAGcA7IKbHNMlhGQ89eRmvpIUGfKWNVdbhSbkQaWhEoMGmGslA==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [freebsd] + + lightningcss-linux-arm-gnueabihf@1.30.2: + resolution: {integrity: sha512-eVdpxh4wYcm0PofJIZVuYuLiqBIakQ9uFZmipf6LF/HRj5Bgm0eb3qL/mr1smyXIS1twwOxNWndd8z0E374hiA==} + engines: {node: '>= 12.0.0'} + cpu: [arm] + os: [linux] + + lightningcss-linux-arm64-gnu@1.30.2: + resolution: {integrity: sha512-UK65WJAbwIJbiBFXpxrbTNArtfuznvxAJw4Q2ZGlU8kPeDIWEX1dg3rn2veBVUylA2Ezg89ktszWbaQnxD/e3A==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + + lightningcss-linux-arm64-musl@1.30.2: + resolution: {integrity: sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + + lightningcss-linux-x64-gnu@1.30.2: + resolution: {integrity: sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + + lightningcss-linux-x64-musl@1.30.2: + resolution: {integrity: sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + + lightningcss-win32-arm64-msvc@1.30.2: + resolution: {integrity: sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [win32] + + lightningcss-win32-x64-msvc@1.30.2: + resolution: {integrity: sha512-5g1yc73p+iAkid5phb4oVFMB45417DkRevRbt/El/gKXJk4jid+vPFF/AXbxn05Aky8PapwzZrdJShv5C0avjw==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [win32] + + lightningcss@1.30.2: + resolution: {integrity: sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ==} + engines: {node: '>= 12.0.0'} + lilconfig@2.1.0: resolution: {integrity: sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==} engines: {node: '>=10'} @@ -4937,22 +5145,42 @@ packages: tailwind-merge@3.3.1: resolution: {integrity: sha512-gBXpgUm/3rp1lMZZrM/w7D8GKqshif0zAymAhbCyIt8KMe+0v9DQ7cdYLR4FHH/cKpdTXb+A/tKKU3eolfsI+g==} + tailwind-merge@3.4.0: + resolution: {integrity: sha512-uSaO4gnW+b3Y2aWoWfFpX62vn2sR3skfhbjsEnaBI81WD1wBLlHZe5sWf0AqjksNdYTbGBEd0UasQMT3SNV15g==} + tailwind-variants@0.3.1: resolution: {integrity: sha512-krn67M3FpPwElg4FsZrOQd0U26o7UDH/QOkK8RNaiCCrr052f6YJPBUfNKnPo/s/xRzNPtv1Mldlxsg8Tb46BQ==} engines: {node: '>=16.x', pnpm: '>=7.x'} peerDependencies: tailwindcss: '*' + tailwind-variants@3.2.2: + resolution: {integrity: sha512-Mi4kHeMTLvKlM98XPnK+7HoBPmf4gygdFmqQPaDivc3DpYS6aIY6KiG/PgThrGvii5YZJqRsPz0aPyhoFzmZgg==} + engines: {node: '>=16.x', pnpm: '>=7.x'} + peerDependencies: + tailwind-merge: '>=3.0.0' + tailwindcss: '*' + peerDependenciesMeta: + tailwind-merge: + optional: true + tailwindcss@3.4.19: resolution: {integrity: sha512-3ofp+LL8E+pK/JuPLPggVAIaEuhvIz4qNcf3nA1Xn2o/7fb7s/TYpHhwGDv1ZU3PkBluUVaF8PyCHcm48cKLWQ==} engines: {node: '>=14.0.0'} hasBin: true + tailwindcss@4.1.18: + resolution: {integrity: sha512-4+Z+0yiYyEtUVCScyfHCxOYP06L5Ne+JiHhY2IjR2KWMIWhJOYZKLSGZaP5HkZ8+bY0cxfzwDE5uOmzFXyIwxw==} + talt@2.4.4: resolution: {integrity: sha512-wyvc4IVzBbgWPqXqQMJNHJvm2shq6t/KoYkeC/qEAtVGxXyFq0y+acRKe5P6M/oJbb+Cp9ol+EK4WDqKiGLNog==} peerDependencies: typescript: ^3.0.0 || ^4.0.0 || ^5.0.0 + tapable@2.3.0: + resolution: {integrity: sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==} + engines: {node: '>=6'} + tar-fs@3.1.1: resolution: {integrity: sha512-LZA0oaPOc2fVo82Txf3gw+AkEd38szODlptMYejQUhndHMLQ9M059uXR+AfS7DNo0NpINvSqDsvyaCrBVkptWg==} @@ -6620,9 +6848,9 @@ snapshots: dependencies: acorn: 8.15.0 - '@sveltejs/adapter-vercel@6.2.0(@sveltejs/kit@2.43.8(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.46.1)(vite@7.3.0(@types/node@25.0.3)(jiti@1.21.7)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.46.1)(vite@7.3.0(@types/node@25.0.3)(jiti@1.21.7)(tsx@4.21.0)(yaml@2.8.1)))(rollup@4.53.4)': + '@sveltejs/adapter-vercel@6.2.0(@sveltejs/kit@2.43.8(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.46.1)(vite@7.3.0(@types/node@25.0.3)(jiti@1.21.7)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.46.1)(vite@7.3.0(@types/node@25.0.3)(jiti@1.21.7)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.1)))(rollup@4.53.4)': dependencies: - '@sveltejs/kit': 2.43.8(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.46.1)(vite@7.3.0(@types/node@25.0.3)(jiti@1.21.7)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.46.1)(vite@7.3.0(@types/node@25.0.3)(jiti@1.21.7)(tsx@4.21.0)(yaml@2.8.1)) + '@sveltejs/kit': 2.43.8(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.46.1)(vite@7.3.0(@types/node@25.0.3)(jiti@1.21.7)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.46.1)(vite@7.3.0(@types/node@25.0.3)(jiti@1.21.7)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.1)) '@vercel/nft': 1.1.1(rollup@4.53.4) esbuild: 0.25.12 transitivePeerDependencies: @@ -6630,11 +6858,11 @@ snapshots: - rollup - supports-color - '@sveltejs/kit@2.43.8(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.46.1)(vite@7.3.0(@types/node@25.0.3)(jiti@1.21.7)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.46.1)(vite@7.3.0(@types/node@25.0.3)(jiti@1.21.7)(tsx@4.21.0)(yaml@2.8.1))': + '@sveltejs/kit@2.43.8(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.46.1)(vite@7.3.0(@types/node@25.0.3)(jiti@1.21.7)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.46.1)(vite@7.3.0(@types/node@25.0.3)(jiti@1.21.7)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.1))': dependencies: '@standard-schema/spec': 1.0.0 '@sveltejs/acorn-typescript': 1.0.6(acorn@8.15.0) - '@sveltejs/vite-plugin-svelte': 6.2.1(svelte@5.46.1)(vite@7.3.0(@types/node@25.0.3)(jiti@1.21.7)(tsx@4.21.0)(yaml@2.8.1)) + '@sveltejs/vite-plugin-svelte': 6.2.1(svelte@5.46.1)(vite@7.3.0(@types/node@25.0.3)(jiti@1.21.7)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.1)) '@types/cookie': 0.6.0 acorn: 8.15.0 cookie: 0.6.0 @@ -6647,37 +6875,125 @@ snapshots: set-cookie-parser: 2.7.1 sirv: 3.0.2 svelte: 5.46.1 - vite: 7.3.0(@types/node@25.0.3)(jiti@1.21.7)(tsx@4.21.0)(yaml@2.8.1) + vite: 7.3.0(@types/node@25.0.3)(jiti@1.21.7)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.1) - '@sveltejs/vite-plugin-svelte-inspector@5.0.1(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.46.1)(vite@7.3.0(@types/node@25.0.3)(jiti@1.21.7)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.46.1)(vite@7.3.0(@types/node@25.0.3)(jiti@1.21.7)(tsx@4.21.0)(yaml@2.8.1))': + '@sveltejs/vite-plugin-svelte-inspector@5.0.1(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.46.1)(vite@7.3.0(@types/node@25.0.3)(jiti@1.21.7)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.46.1)(vite@7.3.0(@types/node@25.0.3)(jiti@1.21.7)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.1))': dependencies: - '@sveltejs/vite-plugin-svelte': 6.2.1(svelte@5.46.1)(vite@7.3.0(@types/node@25.0.3)(jiti@1.21.7)(tsx@4.21.0)(yaml@2.8.1)) + '@sveltejs/vite-plugin-svelte': 6.2.1(svelte@5.46.1)(vite@7.3.0(@types/node@25.0.3)(jiti@1.21.7)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.1)) debug: 4.4.3 svelte: 5.46.1 - vite: 7.3.0(@types/node@25.0.3)(jiti@1.21.7)(tsx@4.21.0)(yaml@2.8.1) + vite: 7.3.0(@types/node@25.0.3)(jiti@1.21.7)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.1) transitivePeerDependencies: - supports-color - '@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.46.1)(vite@7.3.0(@types/node@25.0.3)(jiti@1.21.7)(tsx@4.21.0)(yaml@2.8.1))': + '@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.46.1)(vite@7.3.0(@types/node@25.0.3)(jiti@1.21.7)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.1))': dependencies: - '@sveltejs/vite-plugin-svelte-inspector': 5.0.1(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.46.1)(vite@7.3.0(@types/node@25.0.3)(jiti@1.21.7)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.46.1)(vite@7.3.0(@types/node@25.0.3)(jiti@1.21.7)(tsx@4.21.0)(yaml@2.8.1)) + '@sveltejs/vite-plugin-svelte-inspector': 5.0.1(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.46.1)(vite@7.3.0(@types/node@25.0.3)(jiti@1.21.7)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.46.1)(vite@7.3.0(@types/node@25.0.3)(jiti@1.21.7)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.1)) debug: 4.4.3 deepmerge: 4.3.1 magic-string: 0.30.21 svelte: 5.46.1 - vite: 7.3.0(@types/node@25.0.3)(jiti@1.21.7)(tsx@4.21.0)(yaml@2.8.1) - vitefu: 1.1.1(vite@7.3.0(@types/node@25.0.3)(jiti@1.21.7)(tsx@4.21.0)(yaml@2.8.1)) + vite: 7.3.0(@types/node@25.0.3)(jiti@1.21.7)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.1) + vitefu: 1.1.1(vite@7.3.0(@types/node@25.0.3)(jiti@1.21.7)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.1)) transitivePeerDependencies: - supports-color + '@svgdotjs/svg.draggable.js@3.0.6(@svgdotjs/svg.js@3.2.5)': + dependencies: + '@svgdotjs/svg.js': 3.2.5 + + '@svgdotjs/svg.filter.js@3.0.9': + dependencies: + '@svgdotjs/svg.js': 3.2.5 + + '@svgdotjs/svg.js@3.2.5': {} + + '@svgdotjs/svg.resize.js@2.0.5(@svgdotjs/svg.js@3.2.5)(@svgdotjs/svg.select.js@4.0.3(@svgdotjs/svg.js@3.2.5))': + dependencies: + '@svgdotjs/svg.js': 3.2.5 + '@svgdotjs/svg.select.js': 4.0.3(@svgdotjs/svg.js@3.2.5) + + '@svgdotjs/svg.select.js@4.0.3(@svgdotjs/svg.js@3.2.5)': + dependencies: + '@svgdotjs/svg.js': 3.2.5 + '@swc/helpers@0.5.17': dependencies: tslib: 2.8.1 - '@tailwindcss/forms@0.5.11(tailwindcss@3.4.19(tsx@4.21.0)(yaml@2.8.1))': + '@tailwindcss/forms@0.5.11(tailwindcss@4.1.18)': dependencies: mini-svg-data-uri: 1.4.4 - tailwindcss: 3.4.19(tsx@4.21.0)(yaml@2.8.1) + tailwindcss: 4.1.18 + + '@tailwindcss/node@4.1.18': + dependencies: + '@jridgewell/remapping': 2.3.5 + enhanced-resolve: 5.18.4 + jiti: 2.6.1 + lightningcss: 1.30.2 + magic-string: 0.30.21 + source-map-js: 1.2.1 + tailwindcss: 4.1.18 + + '@tailwindcss/oxide-android-arm64@4.1.18': + optional: true + + '@tailwindcss/oxide-darwin-arm64@4.1.18': + optional: true + + '@tailwindcss/oxide-darwin-x64@4.1.18': + optional: true + + '@tailwindcss/oxide-freebsd-x64@4.1.18': + optional: true + + '@tailwindcss/oxide-linux-arm-gnueabihf@4.1.18': + optional: true + + '@tailwindcss/oxide-linux-arm64-gnu@4.1.18': + optional: true + + '@tailwindcss/oxide-linux-arm64-musl@4.1.18': + optional: true + + '@tailwindcss/oxide-linux-x64-gnu@4.1.18': + optional: true + + '@tailwindcss/oxide-linux-x64-musl@4.1.18': + optional: true + + '@tailwindcss/oxide-wasm32-wasi@4.1.18': + optional: true + + '@tailwindcss/oxide-win32-arm64-msvc@4.1.18': + optional: true + + '@tailwindcss/oxide-win32-x64-msvc@4.1.18': + optional: true + + '@tailwindcss/oxide@4.1.18': + optionalDependencies: + '@tailwindcss/oxide-android-arm64': 4.1.18 + '@tailwindcss/oxide-darwin-arm64': 4.1.18 + '@tailwindcss/oxide-darwin-x64': 4.1.18 + '@tailwindcss/oxide-freebsd-x64': 4.1.18 + '@tailwindcss/oxide-linux-arm-gnueabihf': 4.1.18 + '@tailwindcss/oxide-linux-arm64-gnu': 4.1.18 + '@tailwindcss/oxide-linux-arm64-musl': 4.1.18 + '@tailwindcss/oxide-linux-x64-gnu': 4.1.18 + '@tailwindcss/oxide-linux-x64-musl': 4.1.18 + '@tailwindcss/oxide-wasm32-wasi': 4.1.18 + '@tailwindcss/oxide-win32-arm64-msvc': 4.1.18 + '@tailwindcss/oxide-win32-x64-msvc': 4.1.18 + + '@tailwindcss/postcss@4.1.18': + dependencies: + '@alloc/quick-lru': 5.2.0 + '@tailwindcss/node': 4.1.18 + '@tailwindcss/oxide': 4.1.18 + postcss: 8.5.6 + tailwindcss: 4.1.18 '@tanstack/react-virtual@3.13.12(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': dependencies: @@ -6711,14 +7027,14 @@ snapshots: dependencies: svelte: 5.46.1 - '@testing-library/svelte@5.3.1(svelte@5.46.1)(vite@7.3.0(@types/node@25.0.3)(jiti@1.21.7)(tsx@4.21.0)(yaml@2.8.1))(vitest@4.0.16)': + '@testing-library/svelte@5.3.1(svelte@5.46.1)(vite@7.3.0(@types/node@25.0.3)(jiti@1.21.7)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.1))(vitest@4.0.16)': dependencies: '@testing-library/dom': 10.4.1 '@testing-library/svelte-core': 1.0.0(svelte@5.46.1) svelte: 5.46.1 optionalDependencies: - vite: 7.3.0(@types/node@25.0.3)(jiti@1.21.7)(tsx@4.21.0)(yaml@2.8.1) - vitest: 4.0.16(@edge-runtime/vm@3.2.0)(@types/node@25.0.3)(@vitest/ui@4.0.16)(jiti@1.21.7)(jsdom@27.4.0)(tsx@4.21.0)(yaml@2.8.1) + vite: 7.3.0(@types/node@25.0.3)(jiti@1.21.7)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.1) + vitest: 4.0.16(@edge-runtime/vm@3.2.0)(@types/node@25.0.3)(@vitest/ui@4.0.16)(jiti@1.21.7)(jsdom@27.4.0)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.1) '@tootallnate/once@2.0.0': {} @@ -7332,7 +7648,7 @@ snapshots: obug: 2.1.1 std-env: 3.10.0 tinyrainbow: 3.0.3 - vitest: 4.0.16(@edge-runtime/vm@3.2.0)(@types/node@25.0.3)(@vitest/ui@4.0.16)(jiti@1.21.7)(jsdom@27.4.0)(tsx@4.21.0)(yaml@2.8.1) + vitest: 4.0.16(@edge-runtime/vm@3.2.0)(@types/node@25.0.3)(@vitest/ui@4.0.16)(jiti@1.21.7)(jsdom@27.4.0)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.1) transitivePeerDependencies: - supports-color @@ -7345,13 +7661,13 @@ snapshots: chai: 6.2.1 tinyrainbow: 3.0.3 - '@vitest/mocker@4.0.16(vite@7.3.0(@types/node@25.0.3)(jiti@1.21.7)(tsx@4.21.0)(yaml@2.8.1))': + '@vitest/mocker@4.0.16(vite@7.3.0(@types/node@25.0.3)(jiti@1.21.7)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.1))': dependencies: '@vitest/spy': 4.0.16 estree-walker: 3.0.3 magic-string: 0.30.21 optionalDependencies: - vite: 7.3.0(@types/node@25.0.3)(jiti@1.21.7)(tsx@4.21.0)(yaml@2.8.1) + vite: 7.3.0(@types/node@25.0.3)(jiti@1.21.7)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.1) '@vitest/pretty-format@4.0.16': dependencies: @@ -7379,7 +7695,7 @@ snapshots: sirv: 3.0.2 tinyglobby: 0.2.15 tinyrainbow: 3.0.3 - vitest: 4.0.16(@edge-runtime/vm@3.2.0)(@types/node@25.0.3)(@vitest/ui@4.0.16)(jiti@1.21.7)(jsdom@27.4.0)(tsx@4.21.0)(yaml@2.8.1) + vitest: 4.0.16(@edge-runtime/vm@3.2.0)(@types/node@25.0.3)(@vitest/ui@4.0.16)(jiti@1.21.7)(jsdom@27.4.0)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.1) '@vitest/utils@4.0.16': dependencies: @@ -7490,6 +7806,15 @@ snapshots: svg.resize.js: 1.4.3 svg.select.js: 3.0.1 + apexcharts@5.3.6: + dependencies: + '@svgdotjs/svg.draggable.js': 3.0.6(@svgdotjs/svg.js@3.2.5) + '@svgdotjs/svg.filter.js': 3.0.9 + '@svgdotjs/svg.js': 3.2.5 + '@svgdotjs/svg.resize.js': 2.0.5(@svgdotjs/svg.js@3.2.5)(@svgdotjs/svg.select.js@4.0.3(@svgdotjs/svg.js@3.2.5)) + '@svgdotjs/svg.select.js': 4.0.3(@svgdotjs/svg.js@3.2.5) + '@yr/monotone-cubic-spline': 1.0.3 + arg@4.1.0: {} arg@4.1.3: {} @@ -8063,6 +8388,8 @@ snapshots: whatwg-mimetype: 4.0.0 whatwg-url: 15.1.0 + date-fns@4.1.0: {} + dayjs@1.11.18: {} debug@4.3.4: @@ -8185,6 +8512,11 @@ snapshots: dependencies: once: 1.4.0 + enhanced-resolve@5.18.4: + dependencies: + graceful-fs: 4.2.11 + tapable: 2.3.0 + entities@6.0.1: {} env-paths@2.2.1: {} @@ -8673,6 +9005,22 @@ snapshots: transitivePeerDependencies: - rollup + flowbite-svelte@1.31.0(rollup@4.53.4)(svelte@5.46.1)(tailwindcss@4.1.18): + dependencies: + '@floating-ui/dom': 1.7.4 + '@floating-ui/utils': 0.2.10 + apexcharts: 5.3.6 + clsx: 2.1.1 + date-fns: 4.1.0 + esm-env: 1.2.2 + flowbite: 3.1.2(rollup@4.53.4) + svelte: 5.46.1 + tailwind-merge: 3.4.0 + tailwind-variants: 3.2.2(tailwind-merge@3.4.0)(tailwindcss@4.1.18) + tailwindcss: 4.1.18 + transitivePeerDependencies: + - rollup + flowbite@2.5.2(rollup@4.53.4): dependencies: '@popperjs/core': 2.11.8 @@ -8681,6 +9029,15 @@ snapshots: transitivePeerDependencies: - rollup + flowbite@3.1.2(rollup@4.53.4): + dependencies: + '@popperjs/core': 2.11.8 + flowbite-datepicker: 1.3.2(rollup@4.53.4) + mini-svg-data-uri: 1.4.4 + postcss: 8.5.6 + transitivePeerDependencies: + - rollup + form-data@4.0.5: dependencies: asynckit: 0.4.0 @@ -9153,6 +9510,55 @@ snapshots: libphonenumber-js@1.12.25: optional: true + lightningcss-android-arm64@1.30.2: + optional: true + + lightningcss-darwin-arm64@1.30.2: + optional: true + + lightningcss-darwin-x64@1.30.2: + optional: true + + lightningcss-freebsd-x64@1.30.2: + optional: true + + lightningcss-linux-arm-gnueabihf@1.30.2: + optional: true + + lightningcss-linux-arm64-gnu@1.30.2: + optional: true + + lightningcss-linux-arm64-musl@1.30.2: + optional: true + + lightningcss-linux-x64-gnu@1.30.2: + optional: true + + lightningcss-linux-x64-musl@1.30.2: + optional: true + + lightningcss-win32-arm64-msvc@1.30.2: + optional: true + + lightningcss-win32-x64-msvc@1.30.2: + optional: true + + lightningcss@1.30.2: + dependencies: + detect-libc: 2.1.2 + optionalDependencies: + lightningcss-android-arm64: 1.30.2 + lightningcss-darwin-arm64: 1.30.2 + lightningcss-darwin-x64: 1.30.2 + lightningcss-freebsd-x64: 1.30.2 + lightningcss-linux-arm-gnueabihf: 1.30.2 + lightningcss-linux-arm64-gnu: 1.30.2 + lightningcss-linux-arm64-musl: 1.30.2 + lightningcss-linux-x64-gnu: 1.30.2 + lightningcss-linux-x64-musl: 1.30.2 + lightningcss-win32-arm64-msvc: 1.30.2 + lightningcss-win32-x64-msvc: 1.30.2 + lilconfig@2.1.0: {} lilconfig@3.1.3: {} @@ -10109,15 +10515,15 @@ snapshots: supports-preserve-symlinks-flag@1.0.0: {} - svelte-5-ui-lib@0.12.2(svelte@5.46.1)(tailwindcss@3.4.19(tsx@4.21.0)(yaml@2.8.1)): + svelte-5-ui-lib@0.12.2(svelte@5.46.1)(tailwindcss@4.1.18): dependencies: '@floating-ui/dom': 1.7.4 apexcharts: 3.54.1 clsx: 2.1.1 svelte: 5.46.1 tailwind-merge: 2.6.0 - tailwind-variants: 0.3.1(tailwindcss@3.4.19(tsx@4.21.0)(yaml@2.8.1)) - tailwindcss: 3.4.19(tsx@4.21.0)(yaml@2.8.1) + tailwind-variants: 0.3.1(tailwindcss@4.1.18) + tailwindcss: 4.1.18 svelte-check@4.3.5(picomatch@4.0.3)(svelte@5.46.1)(typescript@5.9.3): dependencies: @@ -10165,9 +10571,9 @@ snapshots: magic-string: 0.30.21 zimmerframe: 1.1.4 - sveltekit-superforms@2.27.4(@sveltejs/kit@2.43.8(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.46.1)(vite@7.3.0(@types/node@25.0.3)(jiti@1.21.7)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.46.1)(vite@7.3.0(@types/node@25.0.3)(jiti@1.21.7)(tsx@4.21.0)(yaml@2.8.1)))(@types/json-schema@7.0.15)(esbuild@0.27.1)(svelte@5.46.1)(typescript@5.9.3): + sveltekit-superforms@2.27.4(@sveltejs/kit@2.43.8(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.46.1)(vite@7.3.0(@types/node@25.0.3)(jiti@1.21.7)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.46.1)(vite@7.3.0(@types/node@25.0.3)(jiti@1.21.7)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.1)))(@types/json-schema@7.0.15)(esbuild@0.27.1)(svelte@5.46.1)(typescript@5.9.3): dependencies: - '@sveltejs/kit': 2.43.8(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.46.1)(vite@7.3.0(@types/node@25.0.3)(jiti@1.21.7)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.46.1)(vite@7.3.0(@types/node@25.0.3)(jiti@1.21.7)(tsx@4.21.0)(yaml@2.8.1)) + '@sveltejs/kit': 2.43.8(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.46.1)(vite@7.3.0(@types/node@25.0.3)(jiti@1.21.7)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.46.1)(vite@7.3.0(@types/node@25.0.3)(jiti@1.21.7)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.1)) devalue: 5.4.2 memoize-weak: 1.0.2 svelte: 5.46.1 @@ -10241,10 +10647,18 @@ snapshots: tailwind-merge@3.3.1: {} - tailwind-variants@0.3.1(tailwindcss@3.4.19(tsx@4.21.0)(yaml@2.8.1)): + tailwind-merge@3.4.0: {} + + tailwind-variants@0.3.1(tailwindcss@4.1.18): dependencies: tailwind-merge: 2.5.4 - tailwindcss: 3.4.19(tsx@4.21.0)(yaml@2.8.1) + tailwindcss: 4.1.18 + + tailwind-variants@3.2.2(tailwind-merge@3.4.0)(tailwindcss@4.1.18): + dependencies: + tailwindcss: 4.1.18 + optionalDependencies: + tailwind-merge: 3.4.0 tailwindcss@3.4.19(tsx@4.21.0)(yaml@2.8.1): dependencies: @@ -10274,10 +10688,14 @@ snapshots: - tsx - yaml + tailwindcss@4.1.18: {} + talt@2.4.4(typescript@5.9.3): dependencies: typescript: 5.9.3 + tapable@2.3.0: {} + tar-fs@3.1.1: dependencies: pump: 3.0.3 @@ -10573,7 +10991,7 @@ snapshots: - supports-color - typescript - vite@7.3.0(@types/node@25.0.3)(jiti@1.21.7)(tsx@4.21.0)(yaml@2.8.1): + vite@7.3.0(@types/node@25.0.3)(jiti@1.21.7)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.1): dependencies: esbuild: 0.27.1 fdir: 6.5.0(picomatch@4.0.3) @@ -10585,17 +11003,18 @@ snapshots: '@types/node': 25.0.3 fsevents: 2.3.3 jiti: 1.21.7 + lightningcss: 1.30.2 tsx: 4.21.0 yaml: 2.8.1 - vitefu@1.1.1(vite@7.3.0(@types/node@25.0.3)(jiti@1.21.7)(tsx@4.21.0)(yaml@2.8.1)): + vitefu@1.1.1(vite@7.3.0(@types/node@25.0.3)(jiti@1.21.7)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.1)): optionalDependencies: - vite: 7.3.0(@types/node@25.0.3)(jiti@1.21.7)(tsx@4.21.0)(yaml@2.8.1) + vite: 7.3.0(@types/node@25.0.3)(jiti@1.21.7)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.1) - vitest@4.0.16(@edge-runtime/vm@3.2.0)(@types/node@25.0.3)(@vitest/ui@4.0.16)(jiti@1.21.7)(jsdom@27.4.0)(tsx@4.21.0)(yaml@2.8.1): + vitest@4.0.16(@edge-runtime/vm@3.2.0)(@types/node@25.0.3)(@vitest/ui@4.0.16)(jiti@1.21.7)(jsdom@27.4.0)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.1): dependencies: '@vitest/expect': 4.0.16 - '@vitest/mocker': 4.0.16(vite@7.3.0(@types/node@25.0.3)(jiti@1.21.7)(tsx@4.21.0)(yaml@2.8.1)) + '@vitest/mocker': 4.0.16(vite@7.3.0(@types/node@25.0.3)(jiti@1.21.7)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.1)) '@vitest/pretty-format': 4.0.16 '@vitest/runner': 4.0.16 '@vitest/snapshot': 4.0.16 @@ -10612,7 +11031,7 @@ snapshots: tinyexec: 1.0.2 tinyglobby: 0.2.15 tinyrainbow: 3.0.3 - vite: 7.3.0(@types/node@25.0.3)(jiti@1.21.7)(tsx@4.21.0)(yaml@2.8.1) + vite: 7.3.0(@types/node@25.0.3)(jiti@1.21.7)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.1) why-is-node-running: 2.3.0 optionalDependencies: '@edge-runtime/vm': 3.2.0 diff --git a/postcss.config.mjs b/postcss.config.mjs index 7093564cd..a34a3d560 100644 --- a/postcss.config.mjs +++ b/postcss.config.mjs @@ -1,11 +1,5 @@ -import tailwindcss from 'tailwindcss'; -import autoprefixer from 'autoprefixer'; - export default { - plugins: [ - //Some plugins, like tailwindcss/nesting, need to run before Tailwind, - tailwindcss, - //But others, like autoprefixer, need to run after, - autoprefixer, - ], + plugins: { + '@tailwindcss/postcss': {}, + }, }; diff --git a/src/app.css b/src/app.css index 526b9ba3e..85454f8bb 100644 --- a/src/app.css +++ b/src/app.css @@ -1,7 +1,11 @@ /* Write your global styles here, in PostCSS syntax */ -@tailwind base; -@tailwind components; -@tailwind utilities; +@import 'tailwindcss'; +@plugin 'flowbite/plugin'; +@custom-variant dark (&:where(.dark, .dark *)); + +@source '../src'; +@source '../node_modules/flowbite-svelte/dist'; +@source '../node_modules/flowbite-svelte-icons/dist'; body { @apply flex flex-col items-center; diff --git a/tailwind.config.ts b/tailwind.config.ts index aef030b9d..efffa328e 100644 --- a/tailwind.config.ts +++ b/tailwind.config.ts @@ -4,11 +4,6 @@ import flowbitePlugin from 'flowbite/plugin'; import type { Config } from 'tailwindcss'; const config = { - content: [ - './src/**/*.{html,js,svelte,ts}', - './node_modules/svelte-5-ui-lib/**/*.{html,js,svelte,ts}', - ], - plugins: [forms, flowbitePlugin], darkMode: 'selector', From c77641cd93b721c3621998da6d03831536b90dee Mon Sep 17 00:00:00 2001 From: Kato Hiroki Date: Sun, 4 Jan 2026 05:44:32 +0000 Subject: [PATCH 08/62] fix: Enable to show custom colors (#2054) --- .../plan.md | 163 +++++++++++++++--- src/app.css | 8 +- 2 files changed, 146 insertions(+), 25 deletions(-) diff --git a/docs/dev-notes/2026-01-02/migrate-from-svelte-5-ui-lib-to-flowbite-svelte/plan.md b/docs/dev-notes/2026-01-02/migrate-from-svelte-5-ui-lib-to-flowbite-svelte/plan.md index 07889f500..9fab476c2 100644 --- a/docs/dev-notes/2026-01-02/migrate-from-svelte-5-ui-lib-to-flowbite-svelte/plan.md +++ b/docs/dev-notes/2026-01-02/migrate-from-svelte-5-ui-lib-to-flowbite-svelte/plan.md @@ -126,6 +126,8 @@ **1. `src/app.css` の記法変更(v3→v4)** +TailwindCSS v4 では CSS-first アーキテクチャを採用。v3 の `@tailwind` ディレクティブから `@import` と CSS ベースの設定に移行します。 + v3: ```css @@ -134,52 +136,54 @@ v3: @tailwind utilities; ``` -v4: +v4(推奨順序): ```css @import 'tailwindcss'; @plugin 'flowbite/plugin'; @custom-variant dark (&:where(.dark, .dark *)); + +@config "../tailwind.config.ts"; +@source "../src/**/*"; +@source "../node_modules/flowbite-svelte/dist/**/*"; ``` -**詳細は:** [Tailwind CSS Upgrade Guide v4](https://tailwindcss.com/docs/upgrade-guide) +**配置順序の説明:** ---- +- `@import 'tailwindcss'` を最初に置いて v4 コア機能を読み込む +- `@plugin` と `@custom-variant` で機能拡張 +- `@config` は末尾に置く(v3 互換モード有効時の標準的な配置) +- `@source` で明示的にスキャン対象を指定 -**2. `tailwind.config.ts` の @config 設定** +**注記:** `@config` が最初にないと、`tailwind.config.ts` の設定(カスタムカラー、breakpoints など)が反映されません。 -v4 では `tailwind.config.ts` を自動認識しない場合がある。 - -```css -/* src/app.css に追加 */ -@config "../tailwind.config.ts"; -``` +**詳細は:** [Tailwind CSS Upgrade Guide v4](https://tailwindcss.com/docs/upgrade-guide) --- -**3. `@source` ディレクティブの追加(重要)** +**2. `tailwind.config.ts` の簡潔化** + +v4 では `content` オプションを削除し、CSS の `@source` で指定します。 -現在、`tailwind.config.ts` の `content` で svelte-5-ui-lib を指定: +v3 形式(削除): ```typescript content: [ './src/**/*.{html,js,svelte,ts}', - './node_modules/svelte-5-ui-lib/**/*.{html,js,svelte,ts}', // ← これをremoveする + './node_modules/svelte-5-ui-lib/**/*.{html,js,svelte,ts}', ], ``` -v4 では CSS の `@source` で指定: +**注記:** -```css -/* src/app.css */ -@source "../src"; -@source "../node_modules/flowbite-svelte/dist"; -@source "../node_modules/flowbite-svelte-icons/dist"; -``` +- CSS の `@source` ディレクティブで指定されるため、`tailwind.config.ts` から `content` オプションを削除します +- `flowbite-svelte-icons` は本プロジェクトで使用していないため(`@lucide/svelte` を使用),`@source` に含めません --- -**4. `postcss.config.mjs` の確認** +**3. `postcss.config.mjs` の v4 対応** + +v4 では PostCSS プラグインを `@tailwindcss/postcss` に変更: ```javascript export default { @@ -713,3 +717,120 @@ Running 10 tests using 3 workers **作成日:** 2026-01-02 **最終更新:** 2026-01-03 **ステータス:** フェーズ0 完了 + +--- + +## 根本原因の特定と解決(2026-01-04 追記・修正版) + +### レイアウト完全崩壊の原因と解決 + +前回のレイアウト崩壊は CSS クラス生成の失敗に起因していました。 + +**初期の誤った理解:** + +- `@config` は必ず最初に記述する必要がある(誤り) +- `@source` でワイルドカードを含める必要がある(誤り) + +**実装から判明した正しい理解:** + +```css +@import 'tailwindcss'; +@plugin 'flowbite/plugin'; +@custom-variant dark (&:where(.dark, .dark *)); + +@config "../tailwind.config.ts"; +@source '../src/**/*'; +@source '../node_modules/flowbite-svelte/dist/**/*'; +``` + +- `tailwind.config.ts` に `content` 配列を追加(v3 互換) + +### 公式ドキュメント根拠 + +1. **@config は「互換性維持ディレクティブ」(レガシー機能)** + + [TailwindCSS Functions and Directives - @config](https://tailwindcss.com/docs/functions-and-directives#config) + + > Use the `@config` directive to load a legacy JavaScript-based configuration file. + > Things defined in CSS will be merged where possible and otherwise take precedence over those defined in configs, presets, and plugins. + + **→ `@config` の配置順序は CSS-first の機能と共存できる限り、厳密には不要** + +2. **@source は「ファイルスキャン範囲の明示」** + + [Detecting classes in source files](https://tailwindcss.com/docs/detecting-classes-in-source-files) + + > Tailwind will scan every file in your project for class names, except in the following cases... If you need to scan any files that Tailwind is ignoring by default, you can explicitly register those sources using `@source`. + + **→ v4 ネイティブのファイルスキャン機構。`content` 配列の代替** + +3. **content 配列は v3 互換モード** + + v4 でも `@config` で v3 形式の設定を読み込む場合、`content` 配列が機能する。これは TailwindCSS が v3 との後方互換性をサポートしているため。 + +### 実装形態:v3/v4 ハイブリッド + +現在の実装は以下のハイブリッド構成: + +``` +v4 ネイティブ部分 v3 互換部分 +───────────────────────────────── +@import 'tailwindcss' ← CSS-first +@plugin 'flowbite' ← CSS-first +@custom-variant dark ← CSS-first + +@config "..." ← JS 設定読み込み(v3 形式) +@source ... ← CSS-first のスキャン指定 +``` + +**なぜ両方必要か:** + +- `@config` で読み込んだ `tailwind.config.ts` の `content` 配列も機能する +- v4 の `@source` も独立して機能する +- 両者が同時に指定されると、スキャン範囲が確実になり安定性が向上 + +### テスト結果 + +✅ **CSS 生成が正常に復旧** + +``` +18/19 テスト PASS + +✅ primary color is generated in CSS +✅ atcoder color is generated +✅ xs breakpoint is available +✅ navbar responsive (lg/mobile) +✅ dark mode toggle +``` + +生成されたCSS(サンプル): + +```css +/* .svelte-kit/.../0.DTkUDpQD.css 内に存在確認 */ +.text-primary-500 { ... } +.bg-atcoder-Q1 { ... } +``` + +### 教訓と推奨 + +1. **@config の位置は「末尾」が安定** + - v4 ネイティブ機能をすべて定義した後に `@config` を置く + - CSS で定義した設定が優先される(公式仕様) + +2. **content 配列は v3 互換モードの証** + - v4 移行中は `content` 配列を保持しておくと安定 + - 完全移行時(v3 互換不要時)は削除可 + +3. **@source の指定は「ワイルドカード含める」推奨** + - `@source '../src'` でも動作するが、`@source '../src/**/*'` の方が明示的 + - 外部ライブラリ(flowbite-svelte)は必ず指定 + +4. **v4 への段階的移行アプローチが有効** + - CSS-first機能(@import, @plugin, @theme)と JS設定(@config)の併用で安定 + - 完全なCSS化は段階的に実施可能 + +--- + +**作成日:** 2026-01-02 +**最終更新:** 2026-01-04(修正版) +**ステータス:** フェーズ0 完了・根本原因特定・公式ドキュメント根拠記載 diff --git a/src/app.css b/src/app.css index 85454f8bb..ac5e8f63a 100644 --- a/src/app.css +++ b/src/app.css @@ -3,9 +3,9 @@ @plugin 'flowbite/plugin'; @custom-variant dark (&:where(.dark, .dark *)); -@source '../src'; -@source '../node_modules/flowbite-svelte/dist'; -@source '../node_modules/flowbite-svelte-icons/dist'; +@config "../tailwind.config.ts"; +@source "../src/**/*"; +@source "../node_modules/flowbite-svelte/dist/**/*"; body { @apply flex flex-col items-center; @@ -17,5 +17,5 @@ body { } #root { - @apply w-full max-w-screen-xl; + @apply w-full max-w-7xl; } From 035aeff9cb0668ab971c6c3317d8e8011202c982 Mon Sep 17 00:00:00 2001 From: Kato Hiroki Date: Sun, 4 Jan 2026 05:46:12 +0000 Subject: [PATCH 09/62] chore(e2e): Fix description (#2054) --- tests/dark-mode.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/dark-mode.spec.ts b/tests/dark-mode.spec.ts index 666cc78ad..32fa0b35f 100644 --- a/tests/dark-mode.spec.ts +++ b/tests/dark-mode.spec.ts @@ -25,7 +25,7 @@ const test = base.extend<{ iPhonePage: Page; desktopPage: Page }>({ }, }); -test.describe('Dark mode - Regression from v3->v4 migration', () => { +test.describe('Dark mode - Regression from tailwindcss v3 to v4 migration', () => { /** * Preconditions: * - Development server started with pnpm dev From 5453b32f029e2b7e299eb98f99b5bcc1291e0ad8 Mon Sep 17 00:00:00 2001 From: Kato Hiroki Date: Sun, 4 Jan 2026 06:04:00 +0000 Subject: [PATCH 10/62] chore(docs): Update plan (#2054) --- .../plan.md | 171 +++++------------- 1 file changed, 43 insertions(+), 128 deletions(-) diff --git a/docs/dev-notes/2026-01-02/migrate-from-svelte-5-ui-lib-to-flowbite-svelte/plan.md b/docs/dev-notes/2026-01-02/migrate-from-svelte-5-ui-lib-to-flowbite-svelte/plan.md index 9fab476c2..1864bd6a9 100644 --- a/docs/dev-notes/2026-01-02/migrate-from-svelte-5-ui-lib-to-flowbite-svelte/plan.md +++ b/docs/dev-notes/2026-01-02/migrate-from-svelte-5-ui-lib-to-flowbite-svelte/plan.md @@ -165,19 +165,11 @@ v4(推奨順序): v4 では `content` オプションを削除し、CSS の `@source` で指定します。 -v3 形式(削除): +**削除項目:** -```typescript -content: [ - './src/**/*.{html,js,svelte,ts}', - './node_modules/svelte-5-ui-lib/**/*.{html,js,svelte,ts}', -], -``` - -**注記:** +- `content` 配列:v4 では不要(`@source` で代替) -- CSS の `@source` ディレクティブで指定されるため、`tailwind.config.ts` から `content` オプションを削除します -- `flowbite-svelte-icons` は本プロジェクトで使用していないため(`@lucide/svelte` を使用),`@source` に含めません +**参考:** [Detecting classes in source files](https://tailwindcss.com/docs/detecting-classes-in-source-files) --- @@ -195,6 +187,40 @@ export default { --- +#### 0-1 実装結果と検証(2026-01-04) + +**実装確認:** + +✅ `src/app.css` を v4 形式に完全移行 +✅ `tailwind.config.ts` から `content` 配列を削除(`@source` で制御) +✅ `postcss.config.mjs` を `@tailwindcss/postcss` で更新 +✅ Flowbite v3.1.2 + flowbite-svelte v1.31.0 インストール + +**テスト結果:** + +``` +18/19 Playwright テスト PASS + +✅ primary color is generated in CSS +✅ atcoder color is generated +✅ xs breakpoint is available +✅ navbar responsive behavior +✅ dark mode toggle +``` + +**設定根拠:** + +- `@config` は末尾に配置(v3 互換設定の安全な読み込み) +- `@source` でスキャン対象を明示(v4 推奨方法) +- `content` 配列は不使用(v4 では CSS-driven) + +**参考ドキュメント:** + +- [Installation - Using PostCSS](https://tailwindcss.com/docs/installation/using-postcss) +- [Functions and Directives - @config](https://tailwindcss.com/docs/functions-and-directives#config) + +--- + #### 0-2. TailwindCSS v4 任意対応(実装は後回し) 以下は「推奨」だが、v4 必須ではない。**UI が安定した後に** 実装: @@ -218,10 +244,10 @@ cat .svelte-kit/output/client/_app/immutable/assets/*.css | grep "@media.*420px" **確認項目:** -- [ ] `text-primary-500` が CSS に含まれる -- [ ] `bg-atcoder-Q1` 等が CSS に含まれる -- [ ] `@media (max-width: 420px)` が CSS に含まれる -- [ ] dev server 起動時に UI が崩れていない(目視確認) +- [x] `text-primary-500` が CSS に含まれる +- [x] `bg-atcoder-Q1` 等が CSS に含まれる +- [x] `@media (max-width: 420px)` が CSS に含まれる +- [x] dev server 起動時に UI が崩れていない(目視確認) **合格基準:** @@ -720,117 +746,6 @@ Running 10 tests using 3 workers --- -## 根本原因の特定と解決(2026-01-04 追記・修正版) - -### レイアウト完全崩壊の原因と解決 - -前回のレイアウト崩壊は CSS クラス生成の失敗に起因していました。 - -**初期の誤った理解:** - -- `@config` は必ず最初に記述する必要がある(誤り) -- `@source` でワイルドカードを含める必要がある(誤り) - -**実装から判明した正しい理解:** - -```css -@import 'tailwindcss'; -@plugin 'flowbite/plugin'; -@custom-variant dark (&:where(.dark, .dark *)); - -@config "../tailwind.config.ts"; -@source '../src/**/*'; -@source '../node_modules/flowbite-svelte/dist/**/*'; -``` - -- `tailwind.config.ts` に `content` 配列を追加(v3 互換) - -### 公式ドキュメント根拠 - -1. **@config は「互換性維持ディレクティブ」(レガシー機能)** - - [TailwindCSS Functions and Directives - @config](https://tailwindcss.com/docs/functions-and-directives#config) - - > Use the `@config` directive to load a legacy JavaScript-based configuration file. - > Things defined in CSS will be merged where possible and otherwise take precedence over those defined in configs, presets, and plugins. - - **→ `@config` の配置順序は CSS-first の機能と共存できる限り、厳密には不要** - -2. **@source は「ファイルスキャン範囲の明示」** - - [Detecting classes in source files](https://tailwindcss.com/docs/detecting-classes-in-source-files) - - > Tailwind will scan every file in your project for class names, except in the following cases... If you need to scan any files that Tailwind is ignoring by default, you can explicitly register those sources using `@source`. - - **→ v4 ネイティブのファイルスキャン機構。`content` 配列の代替** - -3. **content 配列は v3 互換モード** - - v4 でも `@config` で v3 形式の設定を読み込む場合、`content` 配列が機能する。これは TailwindCSS が v3 との後方互換性をサポートしているため。 - -### 実装形態:v3/v4 ハイブリッド - -現在の実装は以下のハイブリッド構成: - -``` -v4 ネイティブ部分 v3 互換部分 -───────────────────────────────── -@import 'tailwindcss' ← CSS-first -@plugin 'flowbite' ← CSS-first -@custom-variant dark ← CSS-first - -@config "..." ← JS 設定読み込み(v3 形式) -@source ... ← CSS-first のスキャン指定 -``` - -**なぜ両方必要か:** - -- `@config` で読み込んだ `tailwind.config.ts` の `content` 配列も機能する -- v4 の `@source` も独立して機能する -- 両者が同時に指定されると、スキャン範囲が確実になり安定性が向上 - -### テスト結果 - -✅ **CSS 生成が正常に復旧** - -``` -18/19 テスト PASS - -✅ primary color is generated in CSS -✅ atcoder color is generated -✅ xs breakpoint is available -✅ navbar responsive (lg/mobile) -✅ dark mode toggle -``` - -生成されたCSS(サンプル): - -```css -/* .svelte-kit/.../0.DTkUDpQD.css 内に存在確認 */ -.text-primary-500 { ... } -.bg-atcoder-Q1 { ... } -``` - -### 教訓と推奨 - -1. **@config の位置は「末尾」が安定** - - v4 ネイティブ機能をすべて定義した後に `@config` を置く - - CSS で定義した設定が優先される(公式仕様) - -2. **content 配列は v3 互換モードの証** - - v4 移行中は `content` 配列を保持しておくと安定 - - 完全移行時(v3 互換不要時)は削除可 - -3. **@source の指定は「ワイルドカード含める」推奨** - - `@source '../src'` でも動作するが、`@source '../src/**/*'` の方が明示的 - - 外部ライブラリ(flowbite-svelte)は必ず指定 - -4. **v4 への段階的移行アプローチが有効** - - CSS-first機能(@import, @plugin, @theme)と JS設定(@config)の併用で安定 - - 完全なCSS化は段階的に実施可能 - ---- - **作成日:** 2026-01-02 -**最終更新:** 2026-01-04(修正版) -**ステータス:** フェーズ0 完了・根本原因特定・公式ドキュメント根拠記載 +**最終更新:** 2026-01-04 +**ステータス:** フェーズ0 完了・実装検証済み From 1dff2f74e639fdb3507406737618494c3199130d Mon Sep 17 00:00:00 2001 From: Kato Hiroki Date: Sun, 4 Jan 2026 06:43:30 +0000 Subject: [PATCH 11/62] breaking: Use flowbite-svelte instead of embla (#2054) --- package.json | 2 -- pnpm-lock.yaml | 40 ---------------------------------------- 2 files changed, 42 deletions(-) diff --git a/package.json b/package.json index 609138942..cc4ddcfb3 100644 --- a/package.json +++ b/package.json @@ -79,8 +79,6 @@ "@types/node": "25.0.3", "autoprefixer": "10.4.23", "debug": "4.4.3", - "embla-carousel-autoplay": "8.6.0", - "embla-carousel-svelte": "8.6.0", "lucia": "2.7.7", "p-queue": "^9.0.1", "playwright": "1.57.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 565de5e12..012935c9d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -38,12 +38,6 @@ importers: debug: specifier: 4.4.3 version: 4.4.3 - embla-carousel-autoplay: - specifier: 8.6.0 - version: 8.6.0(embla-carousel@8.6.0) - embla-carousel-svelte: - specifier: 8.6.0 - version: 8.6.0(svelte@5.46.1) lucia: specifier: 2.7.7 version: 2.7.7 @@ -3048,24 +3042,6 @@ packages: electron-to-chromium@1.5.267: resolution: {integrity: sha512-0Drusm6MVRXSOJpGbaSVgcQsuB4hEkMpHXaVstcPmhu5LIedxs1xNK/nIxmQIU/RPC0+1/o0AVZfBTkTNJOdUw==} - embla-carousel-autoplay@8.6.0: - resolution: {integrity: sha512-OBu5G3nwaSXkZCo1A6LTaFMZ8EpkYbwIaH+bPqdBnDGQ2fh4+NbzjXjs2SktoPNKCtflfVMc75njaDHOYXcrsA==} - peerDependencies: - embla-carousel: 8.6.0 - - embla-carousel-reactive-utils@8.6.0: - resolution: {integrity: sha512-fMVUDUEx0/uIEDM0Mz3dHznDhfX+znCCDCeIophYb1QGVM7YThSWX+wz11zlYwWFOr74b4QLGg0hrGPJeG2s4A==} - peerDependencies: - embla-carousel: 8.6.0 - - embla-carousel-svelte@8.6.0: - resolution: {integrity: sha512-ZDsKk8Sdv+AUTygMYcwZjfRd1DTh+JSUzxkOo8b9iKAkYjg+39mzbY/lwHsE3jXSpKxdKWS69hPSNuzlOGtR2Q==} - peerDependencies: - svelte: ^3.49.0 || ^4.0.0 || ^5.0.0 - - embla-carousel@8.6.0: - resolution: {integrity: sha512-SjWyZBHJPbqxHOzckOfo8lHisEaJWmwd23XppYFYVh10bU66/Pn5tkVkbkCMZVdbUE5eTCI2nD8OyIP4Z+uwkA==} - emoji-regex@8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} @@ -8484,22 +8460,6 @@ snapshots: electron-to-chromium@1.5.267: {} - embla-carousel-autoplay@8.6.0(embla-carousel@8.6.0): - dependencies: - embla-carousel: 8.6.0 - - embla-carousel-reactive-utils@8.6.0(embla-carousel@8.6.0): - dependencies: - embla-carousel: 8.6.0 - - embla-carousel-svelte@8.6.0(svelte@5.46.1): - dependencies: - embla-carousel: 8.6.0 - embla-carousel-reactive-utils: 8.6.0(embla-carousel@8.6.0) - svelte: 5.46.1 - - embla-carousel@8.6.0: {} - emoji-regex@8.0.0: {} empathic@2.0.0: {} From 62fa74c3d39f5fa01790843a9816e70c9ee42f88 Mon Sep 17 00:00:00 2001 From: Kato Hiroki Date: Sun, 4 Jan 2026 06:47:47 +0000 Subject: [PATCH 12/62] breaking: Replace from Svelte 5 UI lib and embla to flowbite-svelte (#2054) --- src/routes/+page.svelte | 33 ++++++++++----------------------- 1 file changed, 10 insertions(+), 23 deletions(-) diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index 9e51b7b54..d15687307 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -1,9 +1,7 @@ @@ -100,23 +95,15 @@

- - - - -
-
- {#each problemImages as problemImage} -
- {problemImage.alt} -
- {/each} -
+
+ + +
From a0d07e022b1b6cb776aa173728b0d9b8d6758b9f Mon Sep 17 00:00:00 2001 From: Kato Hiroki Date: Sun, 4 Jan 2026 07:01:34 +0000 Subject: [PATCH 13/62] chore(docs): Update plan (#2054) --- .../component-mapping.md | 108 +++++++++++++++++- .../plan.md | 100 ++++++++++++---- 2 files changed, 183 insertions(+), 25 deletions(-) diff --git a/docs/dev-notes/2026-01-02/migrate-from-svelte-5-ui-lib-to-flowbite-svelte/component-mapping.md b/docs/dev-notes/2026-01-02/migrate-from-svelte-5-ui-lib-to-flowbite-svelte/component-mapping.md index d32ccdb8b..d6d2d94bd 100644 --- a/docs/dev-notes/2026-01-02/migrate-from-svelte-5-ui-lib-to-flowbite-svelte/component-mapping.md +++ b/docs/dev-notes/2026-01-02/migrate-from-svelte-5-ui-lib-to-flowbite-svelte/component-mapping.md @@ -357,6 +357,110 @@ import { Carousel, Controls, CarouselIndicators } from 'flowbite-svelte'; --- +## カテゴリ3:外部ライブラリ復帰(⭐⭐ 難易度中) + +| コンポーネント | 変更内容 | 詳細 | +| -------------- | ------------------------------------------------ | --------------------------------------------------- | +| **Carousel** | embla-carousel-svelte → Flowbite Svelte Carousel | Plugin-based → Prop-based API、自動スケーリング無し | + +### Carousel プロパティ対応表 + +| 項目 | embla-carousel-svelte | Flowbite Carousel | 説明 | +| ---------------------- | ------------------------------------------------ | -------------------------------------------- | ------------------------------------------------------------- | +| **基本API** | `use:emblaCarouselSvelte={{ options, plugins }}` | `` | embla: action directive / Flowbite: component-based | +| **自動スライド** | `Autoplay()` plugin | `duration` prop | embla: plugin系 / Flowbite: prop単位で制御 | +| **ループ動作** | `options = { loop: true }` | デフォルト有効 | Flowbite は常にループ(設定不可) | +| **画像配列形式** | `[{ src: '...', alt: '...' }]` | `[{ src: '...', alt: '...', title: '...' }]` | **互換性あり**(同一形式) | +| **画像スケーリング** | `imgClass="object-contain h-full w-fit"` | `slideFit="contain"` | embla: CSS class管理 / Flowbite: prop制御 | +| **レスポンシブ高さ** | 外側div に手動で class 設定 | `class="min-h-[300px] xs:min-h-[400px]..."` | どちらも外側divで制御必須 | +| **Overflow 処理** | 外側 div に `overflow-hidden` | 内部処理あり + 明示的推奨 | Flowbite内部処理だが、CSS overrides対応のため明示的指定が安全 | +| **Alt 属性** | 手動設定(`imgClass` 別管理) | `images` 配列内に `alt` 含める | **自動適用**(Slide.svelte で自動反映) | +| **インジケータ表示** | 手動実装が必要 | `` | Flowbite が提供(コンポーネント化) | +| **ナビゲーション矢印** | 手動実装が必要 | `` (任意) | Flowbite が提供(optional) | + +### 移行実装例 + +**Before (embla-carousel-svelte v8.6.0)** + +```svelte + + +
+
+ {#each problemImages as image} +
+ {image.alt} +
+ {/each} +
+
+``` + +**After (Flowbite Carousel v1.31.0)** ✅ + +```svelte + + +
+ + + +
+``` + +### 移行時の注意点 + +1. **Plugin-based → Prop-based への設計変更** + - `Autoplay()` plugin → `duration` prop(ミリ秒単位) + - 簡潔だが、細かい制御が必要な場合は Flowbite API では対応不可 + +2. **自動スケーリング不可** + - embla: `imgClass` で自動管理 + - Flowbite: `slideFit` prop で明示的に指定が必要 + +3. **レスポンシブクラスは手動指定** + - 外側 div の `class` prop に`min-h-[300px] xs:min-h-[400px]` など記載必須 + - embla同様、Flowbite も内部では自動生成されない + +4. **Alt 属性は自動適用** ✅ + - `images` 配列の各オブジェクトに `alt` を含める + - Slide.svelte 内で `{...image}` で展開されるため自動で反映 + +5. **Overflow 処理は明示的に指定** ✅ + - Flowbite 内部で処理される可能性だが、CSS overrides に対応するため外側 div に `overflow-hidden` を追加推奨 + +### 教訓 + +- **API 設計の違いを理解することの重要性**: Plugin-based と Prop-based では柔軟性が異なる +- **ドキュメント不足時はソースコード確認が必須**: alt属性の自動適用はドキュメント未記載だったが GitHub で確認可能 +- **Canonical CSS classes の使用**: Tailwind v4 では `min-h-[300px]` 形式が推奨される(VSCode拡張で警告あり) + +--- + **作成日:** 2026-01-02 -**最終更新:** 2026-01-02 -**ステータス:** ドラフト完成 + +**最終更新:** 2026-01-04 + +**ステータス:** カテゴリ3 実装完了、ドキュメント更新完了 diff --git a/docs/dev-notes/2026-01-02/migrate-from-svelte-5-ui-lib-to-flowbite-svelte/plan.md b/docs/dev-notes/2026-01-02/migrate-from-svelte-5-ui-lib-to-flowbite-svelte/plan.md index 1864bd6a9..5737fb51a 100644 --- a/docs/dev-notes/2026-01-02/migrate-from-svelte-5-ui-lib-to-flowbite-svelte/plan.md +++ b/docs/dev-notes/2026-01-02/migrate-from-svelte-5-ui-lib-to-flowbite-svelte/plan.md @@ -299,6 +299,7 @@ find src -name "*.svelte" -type f \ pnpm test:unit # Vitest pnpm playwright test tests/navbar.spec.ts # Playwright pnpm playwright test tests/custom-colors.spec.ts # Playwright +pnpm playwright test tests/dark-mode.spec.ts # Playwright ``` **工数:** 1-2 days @@ -319,34 +320,99 @@ pnpm playwright test tests/custom-colors.spec.ts # Playwright --- -**段階 1-3: Carousel 置き換え** +**段階 1-3: Carousel 置き換え** ✅ **2026-01-04 完了** `embla-carousel-svelte` → `Flowbite Svelte Carousel` へ置き換え -**API 差分:** +**実装内容:** -- v4: `bind:index` で slide 管理 -- `Controls`, `CarouselIndicators` コンポーネントの組み合わせ +- ✅ embla-carousel-svelte, embla-carousel-autoplay をアンインストール +- ✅ Flowbite Carousel に置き換え +- ✅ CSS クラス互換性確認: + - ✅ レスポンシブ高さ:`min-h-[300px] xs:min-h-[400px] md:min-h-[540px]` 追加 + - ✅ レスポンシブマージン:`mb-8 xs:mb-12` 追加 + - ✅ `overflow-hidden` 追加(内部処理 + 安全性確保) + - ✅ `slideFit="contain"` で image scaling 制御 + - ✅ `alt` 属性:image オブジェクトプロパティで自動適用 +- ✅ ビルド確認・成功 +- ✅ package.json から embla パッケージ削除確認 + +**詳細:** component-mapping.md の「カテゴリ3」セクション参照 **参考:** https://flowbite-svelte.com/docs/components/carousel -**工数:** 1-2 days +**工数:** 1-2 days ✅ --- -**段階 1-4: 複雑なコンポーネント(Dropdown, Modal, Toast)** +**段階 1-4: 複雑なコンポーネント(Dropdown, Modal, Toast)** 🔄 **Pending** + +- 🔄 `Dropdown`: v5 runes `$state(isOpen)` で管理 → Header.svelte で stub 化 +- 🔄 `Modal`: native `` + `form` prop + `onaction` callback +- 🔄 `Toast`: `ToastContainer` で位置管理、auto-dismiss は手動 +- 🔄 `Spinner`, `ButtonGroup`, `Footer`: シンプル置き換え + +**現状:** -- `Dropdown`: v5 runes `$state(isOpen)` で管理 -- `Modal`: native `` + `form` prop + `onaction` callback -- `Toast`: `ToastContainer` で位置管理、auto-dismiss は手動 -- `Spinner`, `ButtonGroup`, `Footer`: シンプル置き換え +- Header.svelte の Dropdown/Modal 機能をコメント化して TODO 化済み +- 後続フェーズで実装予定 **参考:** Flowbite Svelte GitHub Repository - https://github.com/themesberg/flowbite-svelte/blob/main/src/lib/dropdown/Dropdown.svelte - https://github.com/themesberg/flowbite-svelte/blob/main/src/lib/modal/Modal.svelte -**工数:** 3-4 days +**工数:** 3-4 days(後日) + +--- + +## 学習ポイント・教訓 + +### UI ライブラリ移行で重要だったこと + +#### 1. **コンポーネント API の差異を先に把握する** + +- embla-carousel: Plugin-based API (`Autoplay()` plugin) +- Flowbite: Prop-based API (`duration` prop) +- **教訓**: 単純な「置き換え」ではなく、設計思想の違いを理解してから実装 + +#### 2. **CSS 自動化の落とし穴** + +- embla: `imgClass="object-contain"` で自動化 +- Flowbite: `slideFit="contain"` prop で制御 +- レスポンシブクラス(`min-h-[300px] xs:min-h-[400px]`)は手動指定必須 +- **教訓**: コンポーネント ライブラリの「自動化範囲」の把握が重要 + +#### 3. **Alt 属性は自動適用される** + +- `images` 配列に `alt` を含めば、Slide.svelte が自動で `...` に反映 +- ドキュメントに明記されていなかったため、ソースコード確認が必要だった +- **教訓**: ドキュメントが不十分な場合は GitHub ソースコードを読むしかない + +#### 4. **Overflow 処理は明示的に指定** + +- Flowbite 内部で処理されているが、CSS overrides に対応するため外側 div に明示的に `overflow-hidden` を追加 +- **教訓**: "内部処理で大丈夫" は信じず、サイドエフェクトを考慮した設定が必要 + +#### 5. **属性名の変更を見落とさない** + +- Tooltip: `type="auto"` → `showOn="hover"` +- Input: `on:change` → `onchange` +- **教訓**: Breaking Changes ドキュメントをリスト化して一括チェックが効果的 + +#### 6. **Tailwind CSS canonical classes の使用** + +- VSCode 拡張機能 `suggestCanonicalClasses` で `min-h-[300px]` 等が推奨される +- Tailwind v4 では arbitrary values は `min-h-[]` 形式が standard +- 標準 spacing scale の値(`min-h-64` など)より、明示的な px 単位が推奨される場合がある +- **教訓**: 拡張機能の警告を無視せず、公式ドキュメントで確認する習慣が重要 + +### 今後の移行作業での活用ポイント + +- **Category 4(Dropdown, Modal, Toast)** では、上記 1-2 の API 差異が大きいため、先に設計思想を理解してから実装 +- **テストファースト戦略**(Phase -1)の有効性が確認できた → TailwindCSS v4 colors の問題を事前に検出できた +- **段階的実装**が効果的 → 各 Category 毎にビルド+テスト実行で早期問題発見 +- **ソースコード読解**: ドキュメント不足の際は実装を進める前に GitHub リポジトリを確認するステップが必須 --- @@ -678,12 +744,6 @@ Running 10 tests using 3 workers --- -**作成日:** 2026-01-02 -**最終更新:** 2026-01-03 -**ステータス:** フェーズ0 完了(TailwindCSS v3→v4 移行完了、ビルド成功) - ---- - ## フェーズ0 実装結果と教訓(2026-01-03) ### 実装内容 @@ -740,12 +800,6 @@ Running 10 tests using 3 workers --- -**作成日:** 2026-01-02 -**最終更新:** 2026-01-03 -**ステータス:** フェーズ0 完了 - ---- - **作成日:** 2026-01-02 **最終更新:** 2026-01-04 **ステータス:** フェーズ0 完了・実装検証済み From 858733346b7816c1c854294b44ac44f2d0f8d4ec Mon Sep 17 00:00:00 2001 From: Kato Hiroki Date: Sun, 4 Jan 2026 09:04:22 +0000 Subject: [PATCH 14/62] breaking: Replace from Svelte 5 UI lib and flowbite-svelte (#2054) --- src/lib/components/Footer.svelte | 6 +++--- src/lib/components/SpinnerWrapper.svelte | 2 +- src/lib/components/TaskTables/TaskTable.svelte | 3 +-- src/lib/components/WorkBooks/WorkBookList.svelte | 15 +++++++++++---- 4 files changed, 16 insertions(+), 10 deletions(-) diff --git a/src/lib/components/Footer.svelte b/src/lib/components/Footer.svelte index 16c76e883..e3ae6b989 100644 --- a/src/lib/components/Footer.svelte +++ b/src/lib/components/Footer.svelte @@ -1,12 +1,12 @@ - -
+ +
diff --git a/src/lib/components/SpinnerWrapper.svelte b/src/lib/components/SpinnerWrapper.svelte index b621849c4..29183fb0d 100644 --- a/src/lib/components/SpinnerWrapper.svelte +++ b/src/lib/components/SpinnerWrapper.svelte @@ -4,7 +4,7 @@ @prop {any} [size=12] - The size of the spinner in pixels --> -{#snippet navLiForDropdown(id: string, description: string, onclick: () => void)} +{#snippet navLiForDropdown(id: string, description: string)} {description} {/snippet} - - {#snippet brand()} - - {PRODUCT_NAME} Logo - - {PRODUCT_NAME} - - - {/snippet} - - - + + + {PRODUCT_NAME} Logo + + {PRODUCT_NAME} + + + + + + {#if $page.data.isAdmin} - {@render navLiForDropdown('nav-dashboard', '管理画面', dropdownForDashboard.toggle)} + {@render navLiForDropdown('nav-dashboard', '管理画面')} - - - {#each navbarDashboardLinks as navbarDashboardLink} - - {navbarDashboardLink.title} - - {/each} - + + {#each navbarDashboardLinks as navbarDashboardLink} + + {navbarDashboardLink.title} + + {/each} {/if} @@ -143,7 +68,7 @@ {navbarLink.title} @@ -153,69 +78,60 @@ ログイン アカウント作成 {:else} - {@render navLiForDropdown('nav-user-page', user.name, () => handleDropdownForUserPage())} + {@render navLiForDropdown('nav-user-page', user.name)} - - - - - 基本設定 - - - - - - + + + 基本設定 + + + + + (isOpenModalForLogout = true)} + class="font-medium py-2 px-4 text-sm text-left text-gray-700 dark:text-white hover:bg-gray-100 dark:hover:bg-gray-600" + > + ログアウト + {/if} - {@render navLiForDropdown('nav-external-links', '外部リンク', dropdownForExternalLinks.toggle)} - - - - {#each externalLinks as externalLink} - - {externalLink.title} - - {/each} - + {@render navLiForDropdown('nav-external-links', '外部リンク')} + + + {#each externalLinks as externalLink} + + {externalLink.title} + + {/each} - + - + +

ログアウトしますか?

-
- + + + +
diff --git a/src/lib/components/InputFieldWrapper.svelte b/src/lib/components/InputFieldWrapper.svelte index cf8a81e11..806c20b54 100644 --- a/src/lib/components/InputFieldWrapper.svelte +++ b/src/lib/components/InputFieldWrapper.svelte @@ -1,7 +1,7 @@ -{imageAlt} +{imageAlt} {#if isLoggedIn} diff --git a/src/lib/components/SubmissionStatus/UpdatingDropdown.svelte b/src/lib/components/SubmissionStatus/UpdatingDropdown.svelte index 06990f568..c035ba1e7 100644 --- a/src/lib/components/SubmissionStatus/UpdatingDropdown.svelte +++ b/src/lib/components/SubmissionStatus/UpdatingDropdown.svelte @@ -12,36 +12,25 @@ } let { taskResult, isLoggedIn, onupdate = () => {} }: Props = $props(); - - let updatingDropdown: UpdatingDropdown; // Component - - - -
+ {/snippet} diff --git a/src/lib/components/ThermometerProgressBar.svelte b/src/lib/components/ThermometerProgressBar.svelte index 22ed04cca..e4861860d 100644 --- a/src/lib/components/ThermometerProgressBar.svelte +++ b/src/lib/components/ThermometerProgressBar.svelte @@ -1,5 +1,5 @@ {#if tooltipContent !== '' && titleId !== ''} - + {tooltipContent} diff --git a/src/lib/components/Trophies/CompletedTasks.svelte b/src/lib/components/Trophies/CompletedTasks.svelte index a70ede92c..e2776b476 100644 --- a/src/lib/components/Trophies/CompletedTasks.svelte +++ b/src/lib/components/Trophies/CompletedTasks.svelte @@ -1,5 +1,5 @@ {#if areAllTasksAccepted(taskResults, allTasks)} - completed workbook + completed workbook {/if} diff --git a/src/lib/components/UserAccountDeletionForm.svelte b/src/lib/components/UserAccountDeletionForm.svelte index 7b26a7cc4..57924ac0a 100644 --- a/src/lib/components/UserAccountDeletionForm.svelte +++ b/src/lib/components/UserAccountDeletionForm.svelte @@ -1,5 +1,5 @@ @@ -43,18 +28,18 @@ - 同意する + 同意する - {#if showDeleteButton} - + {#if isShownButtonForDelete} + - +

{username}さん、本当に削除しますか?

- +
diff --git a/src/lib/components/UserProfile.svelte b/src/lib/components/UserProfile.svelte index c1f7c52fd..e242bd727 100644 --- a/src/lib/components/UserProfile.svelte +++ b/src/lib/components/UserProfile.svelte @@ -1,5 +1,5 @@

以下の内容が削除されます (データの復元は困難です)

diff --git a/src/lib/components/WorkBook/CommentAndHint.svelte b/src/lib/components/WorkBook/CommentAndHint.svelte index 0a36e3d53..425f28d4e 100644 --- a/src/lib/components/WorkBook/CommentAndHint.svelte +++ b/src/lib/components/WorkBook/CommentAndHint.svelte @@ -1,5 +1,5 @@ + + + + About + + + + + + + + + + About + + + + +``` + +**主な変更点:** + +- `navStatus`, `toggleNav`, `closeNav` → 廃止(`NavHamburger` が内部管理) +- `aClass` → `activeClass` に rename +- `NavHamburger` 新規追加(モバイル対応のハンバーガーメニュー) +- Navbar の内部 state 管理が簡潔に + +**テスト:** Playwright navbar responsive + dropdown trigger test + +--- **対応例:Tabs** @@ -174,15 +222,244 @@ import { Carousel, Controls, CarouselIndicators } from 'flowbite-svelte'; **主な変更点:** -- `DropdownUl` / `DropdownLi` → `DropdownItem` に統合 -- `uiHelpers()` → `$state(isOpen)` runes で管理 -- `bind:isOpen` でバインド -- slot ではなく component の直接配置 +- `DropdownUl` / `DropdownLi` → `DropdownUl`/`DropdownLi` (Flowbite でも互換) +- `uiHelpers()` → `triggeredBy` prop で target selector 指定 +- Floating UI が自動ポジショニングを処理(複雑な CSS クラス不要) +- `bind:isOpen` で内部状態管理 + +#### Header.svelte における `triggeredBy` パターン + +**Before (svelte-5-ui-lib + 複雑な CSS positioning):** + +```svelte + + + (dropdownForDashboard.open = !dropdownForDashboard.open)} +> + 管理画面 + + + + + + Submissions + Users + + +``` + +**After (Flowbite Svelte + `triggeredBy` + Floating UI):** + +```svelte + + + + 管理画面 + + + + + + Submissions + Users + + + + + + ユーザーページ + + + + + + Profile + + +``` -**参考:** [Flowbite Dropdown](https://flowbite-svelte.com/docs/components/dropdown) +**主要改善点:** + +1. **ポジショニングの簡略化**: `left-32 mt-0 lg:-left-10 lg:mt-10` → `w-48 z-20` に削減 +2. **状態管理の削除**: `uiHelpers()` で 3つのオブジェクト管理 → Floating UI に委譲 +3. **ID ベース選択**: `triggeredBy="#nav-dashboard"` で CSS selector に統一 +4. **保守性向上**: trigger 要素と Dropdown の関連付けが明示的 + +**参考:** [Flowbite Dropdown](https://flowbite-svelte.com/docs/components/dropdown) / [Floating UI Placement](https://floating-ui.com/docs/placement) --- +#### Dropdown ベースのカスタムコンポーネント(⭐⭐ 難易度中) + +| コンポーネント | 変更内容 | 詳細 | +| --------------------- | ------------------------------------- | ----------------------------------------------------------- | +| **UpdatingDropdown** | `uiHelpers()` 削除 + trigger 内部移動 | `triggeredBy` CSS selector で自動制御、位置管理ロジック削除 | +| **TaskTableBodyCell** | `bind:this` 削除、trigger ボタン削除 | UpdatingDropdown に trigger 統合、呼び出しを簡潔化 | + +### UpdatingDropdown + TaskTableBodyCell 移行実装例 + +**変更内容:** + +1. **位置管理ロジックの削除** + - `calculateDropdownPosition()`, `updateDropdownPosition()` 等をコメントアウト + - `uiHelpers()` と `handleDropdownBehavior` action を削除 + - CSS 変数 `--dropdown-x`, `--dropdown-y` の設定を削除 + - → Floating UI が自動的にビューポート外での調整を担当 + +2. **trigger ボタンの内部移動** + - 親側: ` + + +``` + +```svelte + + + +
+ + + {#each submissionStatusOptions as submissionStatus} + handleClick(submissionStatus)}> + {submissionStatus.labelName} + + {/each} + + +
+``` + +**After (Flowbite Svelte v1.31.0)** ✅ + +```svelte + + + + +``` + +```svelte + + + +
+ +
+ +
+ + + + {#if isLoggedIn} + {#each submissionStatusOptions as submissionStatus} + handleClick(submissionStatus)}> +
+ {submissionStatus.labelName} + {#if taskResult.status_name === submissionStatus.innerName} + + {/if} +
+
+ {/each} + {:else} + アカウント作成 + + ログイン + {/if} +
+
+ +{#if showForm && selectedSubmissionStatus} + {@render submissionStatusForm(taskResult, selectedSubmissionStatus)} +{/if} +``` + +### 移行時の注意点 + +1. **trigger ID の一意性確保** ✅ + - `Math.random().toString(36).substring(2)` で unique ID 生成 + - 複数の UpdatingDropdown インスタンスでも selector が重複しない + +2. **アクセシビリティ対応** ✅ + - trigger `
` に `role="button"`, `tabindex="0"`, `aria-label` を付与 + - ブラウザのデフォルトボタンスタイルを避けつつ、キーボード操作対応 + +3. **スタイル統一** ✅ + - trigger: `hover:bg-gray-200 dark:hover:bg-gray-700` でホバー状態表示 + - Dropdown: `class="w-32 z-50"` で幅と重なり順序を指定 + - 既存の見た目を保持 + +4. **位置管理ロジックはコメント保持** ✅ + - Floating UI の自動ポジショニングに任せる + - 将来的にカスタム配置が必要になった場合の参考用にコメント保持 + +### 教訓 + +- **trigger の責任分離**: 親から trigger ボタン管理を削除し、コンポーネント内で完結 → 呼び出し側が単純化 +- **CSS selector ベースの制御**: `bind:this` や export function より、`triggeredBy` selector による自動制御が保守性↑ +- **Floating UI の活用**: ビューポート外での自動調整により、複雑な位置計算が不要 + ### 4-2. Modal **差分の大きさ:** 🟡 中(native `` ベース) @@ -190,39 +467,137 @@ import { Carousel, Controls, CarouselIndicators } from 'flowbite-svelte'; **主な変更点:** - native HTML `` element ベース -- `form` prop で内部フォーム自動生成 -- `bind:open` でバインド -- `onaction` callback で submit/cancel 処理 -- `uiHelpers()` 不要(フォーカストラップ、outside click は native で処理) +- `$state(open)` runes で状態管理(uiHelpers 削除) +- `bind:open` で Modal の可視性制御 +- SvelteKit Form Actions (`use:enhance`) との共存 +- フォーカストラップ、outside click は native で自動処理 -**Flowbite Svelte:** +### Modal 状態管理パターン比較表 + +| 項目 | svelte-5-ui-lib | Flowbite Svelte | 説明 | +| ---------------------- | --------------------------------------------- | ------------------------------------------- | ---------------------------------- | +| **状態管理** | `uiHelpers()` で `open/close` 関数実装 | `$state(open)` runes で boolean 管理 | Flowbite は単純な boolean binding | +| **バインド方式** | Custom `modalStatus` prop + 関数呼び出し | `bind:open` で双方向バインド | UI state の管理のみ | +| **フォーム統合** | 手動 `` タグで別管理 | 手動 `` タグで管理 | SvelteKit Form Actions と共存 | +| **フォーム送信** | `onsubmit` handler + `event.preventDefault()` | `use:enhance` で server action 自動処理 | server-side form submission に対応 | +| **モーダルクローズ** | `closeModal()` 関数を明示呼び出し | form success 後に `modalOpen = false` 設定 | `use:enhance` result で制御 | +| **フッターレンダリ** | Slot で custom レンダリング | Button を form 内に直接配置 | form submit button として機能 | +| **フォーカストラップ** | `uiHelpers()` で実装 | native `` が自動処理 | HTML5仕様で自動 | +| **Outside Click** | `uiHelpers()` で実装 | `outsideclose` prop で制御(default: true) | clickoutside での dismiss 可能 | + +### Modal 実装例 + +**Before (svelte-5-ui-lib)** ```svelte - + + + + + + + + + +``` + +**After (Flowbite Svelte v1.31.0)** ✅ - { - if (action === 'accept') { - console.log('Accepted'); +```svelte + + + + + +
+ + +
``` -**参考:** [Flowbite Modal](https://flowbite-svelte.com/docs/components/modal) +### Modal 実装時の注意点 + +1. **`form` prop は不要** + - Flowbite の `form` prop は「クライアント側の form validation UI」に特化 + - SvelteKit server action には向かない + - 手動で `
` を用意すること + +2. **`bind:open` は UI state のみ** + - Modal の可視性を制御するだけ + - form submission とは独立 + +3. **`use:enhance` で server action を自動処理** + + ```typescript + async function handleSubmit(event: Event) { + event.preventDefault(); + const response = await fetch('?/update', { ... }); + if (response.ok) { + modalOpen = false; // form success 時のみ close + } + } + ``` + +4. **`outsideclose` で dismiss 制御** + - `outsideclose={true}` : outside click で modal close 可能(デフォルト) + - `outsideclose={false}` : outside click を無視 + +5. **フォーカストラップと keyboard 処理** + - native `` の focustrap は自動 + - Esc キーでも close 可能(HTML5 standard) + +6. **エラーハンドリングは手動** + - `use:enhance` では server error を自動処理しない + - try-catch で HTTP error をキャッチして処理 + +### 参考資料 + +- [Flowbite Modal](https://flowbite-svelte.com/docs/components/modal) +- [HTML5 `` element](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/dialog) +- [SvelteKit Form Actions use:enhance](https://kit.svelte.dev/docs/form-actions) --- diff --git a/docs/dev-notes/2026-01-02/migrate-from-svelte-5-ui-lib-to-flowbite-svelte/plan.md b/docs/dev-notes/2026-01-02/migrate-from-svelte-5-ui-lib-to-flowbite-svelte/plan.md index 7560a4a77..aa7489b6f 100644 --- a/docs/dev-notes/2026-01-02/migrate-from-svelte-5-ui-lib-to-flowbite-svelte/plan.md +++ b/docs/dev-notes/2026-01-02/migrate-from-svelte-5-ui-lib-to-flowbite-svelte/plan.md @@ -208,6 +208,37 @@ export default { ✅ dark mode toggle ``` +--- + +### フェーズ-1.5: Header.svelte Navbar 実装(2026-01-04) + +**目的:** Navbar コンポーネントをフローブサイト Svelte に移行し、実装方法を確立する + +**実装内容:** + +1. `NavHamburger` 追加(モバイル対応) +2. `navStatus`, `toggleNav`, `closeNav` 削除 → `NavHamburger` 内部管理に委譲 +3. `aClass` → `activeClass` に統一 +4. `$app/stores` 継続使用(SSR 互換性確保) + +**主な学習点:** + +- **State 管理の簡潔化**: svelte-5-ui-lib の独自 state 管理が Flowbite Svelte 内部管理に統一され、コード行数削減 +- **Props 名の確認重要**: `aClass`(svelte-5-ui-lib 固有)→ `activeClass`(Flowbite Svelte 標準)への変更は、デバッグ時に GitHub の NavLi コンポーネント定義確認が必須 +- **$app/state vs $app/stores**: Flowbite Svelte ドキュメント例は client-only デモのため `$app/state` 使用だが、SSR 環境では `$app/stores` が必須 + +**成果:** + +✅ Header.svelte ビルド成功 +✅ Navbar, NavBrand, NavUl, NavLi, NavHamburger, Dropdown 等が Flowbite Svelte で正常動作確認 +✅ component-mapping.md に Navbar 関連実装例を記載 + +**次ステップ:** Navbar responsive + dropdown trigger の E2E テスト実装 + +--- + +### フェーズ0:TailwindCSS v4 移行 + **設定根拠:** - `@config` は末尾に配置(v3 互換設定の安全な読み込み) @@ -345,24 +376,45 @@ pnpm playwright test tests/dark-mode.spec.ts # Playwright --- -**段階 1-4: 複雑なコンポーネント(Dropdown, Modal, Toast)** 🔄 **Pending** +**段階 1-4: 複雑なコンポーネント(Dropdown, Modal, Toast)** ✅ **完了(2026-01-05)** -- 🔄 `Dropdown`: v5 runes `$state(isOpen)` で管理 → Header.svelte で stub 化 -- 🔄 `Modal`: native `` + `form` prop + `onaction` callback -- 🔄 `Toast`: `ToastContainer` で位置管理、auto-dismiss は手動 -- 🔄 `Spinner`, `ButtonGroup`, `Footer`: シンプル置き換え +- ✅ `Modal`: native `` + `$state(open)` + SvelteKit Form Actions 共存 + - UpdatingModal.svelte: `form` prop 不使用、`bind:open` で UI 管理、`use:enhance` で server action 処理 + - `handleSubmit()` で form submit + error handling を統一 + - `modalOpen = false` で成功時のみ close + - ✅ UserAccountDeletionForm.svelte: 同パターンで修正完了(FormWrapper 活用) + - ✅ Header.svelte: logout modal も同パターンで修正完了(`action="../../logout"` 相対パス指定) +- ✅ `Dropdown`: Flowbite Svelte `triggeredBy` + Floating UI 自動ポジショニング + - ✅ Header.svelte: 3つの Dropdown(Dashboard, User Page, External Links)を `triggeredBy` 方式に移行 + - ✅ 複雑な CSS positioning を削除し、Floating UI の自動ポジショニングに任せる + - ✅ `uiHelpers()` による状態管理を廃止、Dropdown が内部で管理 + - ✅ Darkmode コンポーネント有効化 +- ✅ `UpdatingDropdown`: Flowbite Svelte `triggeredBy` + Floating UI による自動ポジショニング + - ✅ UpdatingDropdown.svelte: `uiHelpers()` 削除、複雑な位置管理ロジックをコメントアウト + - ✅ trigger ボタンをコンポーネント内部に移動(`
` で実装) + - ✅ `` で Floating UI の自動ポジショニングに統一 + - ✅ `DropdownItem` で統一(svelte-5-ui-lib の `DropdownLi`, `DropdownUl` から置き換え) + - ✅ TaskTableBodyCell.svelte: `bind:this={updatingDropdown}` 削除、trigger ボタン削除(UpdatingDropdown 内部に統合) +- 🔄 `Toast`: `ToastContainer` で位置管理、auto-dismiss は手動(フェーズ2) +- ✅ `Spinner`, `ButtonGroup`, `Footer`: シンプル置き換え完了(フェーズ1-4) -**現状:** +**完了内容:** -- Header.svelte の Dropdown/Modal 機能をコメント化して TODO 化済み -- 後続フェーズで実装予定 +- ✅ component-mapping.md に Modal 実装パターンを修正:`form` prop 不要、SvelteKit Form Actions との共存に焦点 +- ✅ component-mapping.md に Dropdown 移行パターンを追加:`triggeredBy` + Floating UI 自動ポジショニング +- ✅ UpdatingModal.svelte 実装完了:`form` 削除、`handleSubmit()` 復活、`bind:open` + `use:enhance` で統合 +- ✅ UserAccountDeletionForm.svelte 修正完了:FormWrapper + Modal + server action の3層統合 +- ✅ Header.svelte 実装完了:Dropdown を `triggeredBy` 方式に統合、Darkmode 有効化、CSS 簡素化 +- ✅ UpdatingDropdown.svelte 実装完了:`triggeredBy` 方式に統一、複雑な位置管理削除、trigger 内部移動 +- ✅ TaskTableBodyCell.svelte 修正完了:`bind:this` 削除、trigger ボタン削除 -**参考:** Flowbite Svelte GitHub Repository +**参考:** Flowbite Modal & Dropdown Documentation -- https://github.com/themesberg/flowbite-svelte/blob/main/src/lib/dropdown/Dropdown.svelte -- https://github.com/themesberg/flowbite-svelte/blob/main/src/lib/modal/Modal.svelte +- https://flowbite-svelte.com/docs/components/modal +- https://flowbite-svelte.com/docs/components/dropdown +- https://kit.svelte.dev/docs/form-actions -**工数:** 3-4 days(後日) +**工数:** 5 hours(Modal 3.5h + Dropdown Header 0.5h + UpdatingDropdown 1h) --- @@ -407,6 +459,57 @@ pnpm playwright test tests/dark-mode.spec.ts # Playwright - 標準 spacing scale の値(`min-h-64` など)より、明示的な px 単位が推奨される場合がある - **教訓**: 拡張機能の警告を無視せず、公式ドキュメントで確認する習慣が重要 +#### 7. **Dropdown: Trigger 責任分離と CSS Selector ベース制御(UpdatingDropdown より)** + +- **従来**: 親コンポーネント側で trigger ボタンを配置し、`bind:this` + export function で制御 + - 親: ` - + +
+ + +
From 5c8aae58c1f8a6db1bb2ad33e02273c828607948 Mon Sep 17 00:00:00 2001 From: Kato Hiroki Date: Sun, 11 Jan 2026 07:03:14 +0000 Subject: [PATCH 27/62] chore: Fix typo (#2054) --- .../migrate-from-svelte-5-ui-lib-to-flowbite-svelte/report.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/dev-notes/2026-01-02/migrate-from-svelte-5-ui-lib-to-flowbite-svelte/report.md b/docs/dev-notes/2026-01-02/migrate-from-svelte-5-ui-lib-to-flowbite-svelte/report.md index 0c86c685c..0d50bae3d 100644 --- a/docs/dev-notes/2026-01-02/migrate-from-svelte-5-ui-lib-to-flowbite-svelte/report.md +++ b/docs/dev-notes/2026-01-02/migrate-from-svelte-5-ui-lib-to-flowbite-svelte/report.md @@ -68,7 +68,7 @@ Tailwind CSS v3.4.19 → v4.1.18 への移行において、本プロジェク - ✅ `shadow-sm` を使用(`shadow-xs` への変更必須) - ✅ `shadow-lg` を使用(そのまま) -- 複수の `rounded`/`rounded-*` を使用(全一括確認必須) +- 複数の `rounded`/`rounded-*` を使用(全一括確認必須) **リスク**: スケール名が全体的に下がるため、視覚的にぼやけたり小さくなる可能性。 From be347a5fcf898eeef8e92d0894b1fb1bd9d50dc4 Mon Sep 17 00:00:00 2001 From: Kato Hiroki Date: Sun, 11 Jan 2026 07:10:40 +0000 Subject: [PATCH 28/62] chore: Fix typo (#2054) --- .../migrate-from-svelte-5-ui-lib-to-flowbite-svelte/report.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/dev-notes/2026-01-02/migrate-from-svelte-5-ui-lib-to-flowbite-svelte/report.md b/docs/dev-notes/2026-01-02/migrate-from-svelte-5-ui-lib-to-flowbite-svelte/report.md index 0d50bae3d..0d5efbd29 100644 --- a/docs/dev-notes/2026-01-02/migrate-from-svelte-5-ui-lib-to-flowbite-svelte/report.md +++ b/docs/dev-notes/2026-01-02/migrate-from-svelte-5-ui-lib-to-flowbite-svelte/report.md @@ -92,7 +92,7 @@ Tailwind CSS v3.4.19 → v4.1.18 への移行において、本プロジェク | デフォルト幅 | 3px | 1px | `ring` のみ使用時は `ring-3` に明示必須 | | デフォルト色 | `blue-500` | `currentColor` | `ring` のみ使用時は色を明示必須 | -**本プロへの影響**: `ring` 単独使用が少ないため低リスク、ただし Flowbite components 内部での使用を確認。 +**本プロジェクトへの影響**: `ring` 単独使用が少ないため低リスク、ただし Flowbite components 内部での使用を確認。 --- From 3d3a1035caad0b906ef525f1ced4f87b61221071 Mon Sep 17 00:00:00 2001 From: Kato Hiroki Date: Sun, 11 Jan 2026 12:19:00 +0000 Subject: [PATCH 29/62] chore(ui): Update utilities (#2054) --- .../report.md | 104 ++++++++++ src/lib/components/AuthForm.svelte | 4 +- src/lib/components/TagForm.svelte | 4 +- src/lib/components/TagListForEdit.svelte | 2 +- src/lib/components/TaskForm.svelte | 52 ++--- .../TaskGrades/GradeGuidelineTable.svelte | 6 +- src/lib/components/TaskList.svelte | 4 +- src/lib/components/TaskListForEdit.svelte | 2 +- src/lib/components/TaskListSorted.svelte | 68 ++++--- .../components/WorkBook/WorkBookForm.svelte | 2 +- .../WorkBookTasks/WorkBookTasksTable.svelte | 192 +++++++++--------- .../WorkBooks/WorkBookBaseTable.svelte | 4 +- .../(admin)/account_transfer/+page.svelte | 88 ++++---- src/routes/workbooks/[slug]/+page.svelte | 4 +- 14 files changed, 325 insertions(+), 211 deletions(-) diff --git a/docs/dev-notes/2026-01-02/migrate-from-svelte-5-ui-lib-to-flowbite-svelte/report.md b/docs/dev-notes/2026-01-02/migrate-from-svelte-5-ui-lib-to-flowbite-svelte/report.md index 0d5efbd29..ef2d071cb 100644 --- a/docs/dev-notes/2026-01-02/migrate-from-svelte-5-ui-lib-to-flowbite-svelte/report.md +++ b/docs/dev-notes/2026-01-02/migrate-from-svelte-5-ui-lib-to-flowbite-svelte/report.md @@ -851,3 +851,107 @@ toChangeTextColorIfNeeds(grade); // "4Q" を渡す(getTaskGradeLabel 済み) **実行日時**: 2026-01-07 **次回アクション**: フェーズ D(Visual Regression テスト)を dev server で実施推奨 + +--- + +## 8. フェーズ D 実装ガイドライン(UI 崩れ修正) + +### 8.1 実施日: 2026-01-11 + +#### 修正内容概要 + +Tailwind CSS v3→v4 の breaking changes により、以下の 3 つの UI 崩れが発生していた: + +1. **Form 要素の余白消失(space-y-\* セレクタ変更)** +2. **テーブル border 色変更(divide-\* デフォルト色変更)** +3. **テーブル下部 border 消失(divide-\* セレクタ変更)** + +#### 修正方法と結果 + +##### 1️⃣ Form の space-y-\* → gap への移行 + +**修正対象** (4 ファイル): + +- `AuthForm.svelte`: `space-y-6` → `gap-6` +- `TaskForm.svelte`: `space-y-4` → `gap-4` +- `WorkBookForm.svelte`: `space-y-4` → `gap-4` +- `account_transfer/+page.svelte`: `space-y-4` → `gap-4` + +**追加修正**: `AuthForm.svelte` の `` に `p-4 sm:p-6 md:p-8` padding 追加 + +**理由**: v4 では space-\* セレクタが「最後の兄弟要素にマージン未適用」へ変更。gap はセレクタ変更の影響を受けないため推奨。 + +##### 2️⃣ divide-y に color 明示 + +**修正対象**: 12 ファイルの `divide-y` → `divide-y divide-gray-200 dark:divide-gray-700` + +**理由**: v4 では divide-\* のデフォルト色が `gray-200` → `currentColor` へ変更。色指定なしだと text color に連動して黒く見える。 + +##### 3️⃣ Table 外側 border に color を明示 + +**問題**: v4 では `border` クラスのデフォルト色が `currentColor` に変更。color 指定なし → text color(黒)に連動 + +**修正対象**: 6 ファイルの `
` → `
` + +修正ファイル: + +- TaskTables/TaskTable.svelte (既に修正) +- TaskForm.svelte (既に修正) +- account_transfer/+page.svelte (既に修正) +- WorkBooks/WorkBookBaseTable.svelte(追加修正) +- workbooks/[slug]/+page.svelte(追加修正) +- TaskList.svelte(追加修正) + +**理由**: Table 外枠の border が黒く見える問題を解決。divide-y で行間 border は制御し、外側 border は明示的に color 指定。 + +#### ビルド結果 + +✅ **ビルド成功**: 16.55 秒で完了(エラー・警告なし) + +--- + +### 8.2 技術的教訓 + +#### 1. セレクタ変更の 2 つの影響 + +| 対象 | v3 → v4 | 結果 | +| ------------------------- | ------------------------------------------------------- | ---------------------------- | +| `space-y-*` / `space-x-*` | `:not([hidden]) ~ :not([hidden])` → `:not(:last-child)` | 最後の要素への margin 消失 | +| `divide-y` / `divide-x` | `:not([hidden]) ~ :not([hidden])` → `:not(:last-child)` | 最後の子要素への border 消失 | + +**対応**: space-_ は `gap` で置き換え。divide-_ は color を明示 + 外側 border を色指定で補完。 + +#### 2. Default color 変更(v4 の CSS-first 設計による) + +| クラス | v3 | v4 | 影響 | +| ---------- | ------------------------------- | ----------------------------------- | ------------------------ | +| `border` | グレー(暗黙) | `currentColor`(text color に連動) | 色指定なしだと黒く見える | +| `divide-*` | `gray-200` / `gray-700`(暗黙) | `currentColor` | 色指定なしだと黒く見える | + +**対応**: 全 border / divide に color を明示的に指定。v4 は「暗黙のデフォルト」がない設計。 + +#### 3. Table 構造の 2 層構成 + +Tailwind v4 では Table のビジュアルが 2 層に分離: + +1. **内部 border**(行間): `` で制御 +2. **外部 border**(表枠): `
` で制御 + +v3 では divide-y だけで完全に見えていたが、v4 では**外側 border を明示的に指定が必須**。 + +--- + +**実行ステータス**: ✅ フェーズ D 修正完了(2026-01-11 更新) + +**修正ファイル数**: 18 個(form 4 + divide-color 11 + border-color 3) + +**修正内容**: + +- ✅ space-y-\* → gap(4 ファイル) +- ✅ divide-y に color 明示(11 ファイル) +- ✅ Table 外側 border に color 明示(6 ファイル) +- ✅ AuthForm Card に padding 追加 + +**ビルド状態**: ✅ 成功(エラー・警告なし) + +**次回**: `pnpm dev` で visual regression テスト実施 diff --git a/src/lib/components/AuthForm.svelte b/src/lib/components/AuthForm.svelte index af4ca3353..fdc44974d 100644 --- a/src/lib/components/AuthForm.svelte +++ b/src/lib/components/AuthForm.svelte @@ -101,8 +101,8 @@
- -
+ +

{title}

diff --git a/src/lib/components/TagForm.svelte b/src/lib/components/TagForm.svelte index 764cd2cbd..aa8dc2a33 100644 --- a/src/lib/components/TagForm.svelte +++ b/src/lib/components/TagForm.svelte @@ -35,7 +35,7 @@ Edit Tag - + tagId {id} @@ -82,7 +82,7 @@ Edit Tag - + {#each tasks as task} diff --git a/src/lib/components/TagListForEdit.svelte b/src/lib/components/TagListForEdit.svelte index 45a67779e..3cc64e9b4 100644 --- a/src/lib/components/TagListForEdit.svelte +++ b/src/lib/components/TagListForEdit.svelte @@ -34,7 +34,7 @@ 公開中 - + {#each tags as tag} diff --git a/src/lib/components/TaskForm.svelte b/src/lib/components/TaskForm.svelte index 62dbdd63b..8b31cae6f 100644 --- a/src/lib/components/TaskForm.svelte +++ b/src/lib/components/TaskForm.svelte @@ -26,7 +26,7 @@ }); - + 問題一覧 @@ -36,30 +36,32 @@ -
- - - タイトル - - {removeTaskIndexFromTitle(task.title, task.task_table_index)} - - - - 出典 - - {addContestNameToTaskIndex(task.contest_id, task.task_table_index)} - - - - グレード - -
+
+ + + + タイトル + + {removeTaskIndexFromTitle(task.title, task.task_table_index)} + + + + 出典 + + {addContestNameToTaskIndex(task.contest_id, task.task_table_index)} + + + + グレード + +
+
diff --git a/src/lib/components/TaskGrades/GradeGuidelineTable.svelte b/src/lib/components/TaskGrades/GradeGuidelineTable.svelte index f248baab3..694460119 100644 --- a/src/lib/components/TaskGrades/GradeGuidelineTable.svelte +++ b/src/lib/components/TaskGrades/GradeGuidelineTable.svelte @@ -25,7 +25,9 @@
-
+
対応グレード - + {#each gradeGuidelineTableData as { point, task, lowerGrade, upperGrade }} {point} diff --git a/src/lib/components/TaskList.svelte b/src/lib/components/TaskList.svelte index 2493da494..dc9929590 100644 --- a/src/lib/components/TaskList.svelte +++ b/src/lib/components/TaskList.svelte @@ -71,7 +71,7 @@ -
+
回答 @@ -86,7 +86,7 @@ - + {#each taskResults as taskResult} 問題名 - + {#each importContests as importContest} {#if importContest.tasks.length > 0} diff --git a/src/lib/components/TaskListSorted.svelte b/src/lib/components/TaskListSorted.svelte index 83a80d062..319e10f12 100644 --- a/src/lib/components/TaskListSorted.svelte +++ b/src/lib/components/TaskListSorted.svelte @@ -22,37 +22,39 @@ -
- - 回答 - 問題名 - 出典 - 更新日時 - +
+
+ + 回答 + 問題名 + 出典 + 更新日時 + - - {#each taskResults as taskResult} - - - {taskResult.submission_status_label_name} - - - - {removeTaskIndexFromTitle(taskResult.title, taskResult.task_table_index)} - - - - {addContestNameToTaskIndex(taskResult.contest_id, taskResult.task_table_index)} - - {taskResult.updated_at.toLocaleString()} - - {/each} - -
+ + {#each taskResults as taskResult} + + + {taskResult.submission_status_label_name} + + + + {removeTaskIndexFromTitle(taskResult.title, taskResult.task_table_index)} + + + + {addContestNameToTaskIndex(taskResult.contest_id, taskResult.task_table_index)} + + {taskResult.updated_at.toLocaleString()} + + {/each} + + +
diff --git a/src/lib/components/WorkBook/WorkBookForm.svelte b/src/lib/components/WorkBook/WorkBookForm.svelte index a3bfa918d..3946bcdb6 100644 --- a/src/lib/components/WorkBook/WorkBookForm.svelte +++ b/src/lib/components/WorkBook/WorkBookForm.svelte @@ -68,7 +68,7 @@
- + diff --git a/src/lib/components/WorkBookTasks/WorkBookTasksTable.svelte b/src/lib/components/WorkBookTasks/WorkBookTasksTable.svelte index bd2e8ae0e..ae6634deb 100644 --- a/src/lib/components/WorkBookTasks/WorkBookTasksTable.svelte +++ b/src/lib/components/WorkBookTasks/WorkBookTasksTable.svelte @@ -147,104 +147,106 @@ 問題一覧({workBookTasksForTable.length} 問) - - - - # - - グレード - - 問題名 - - - - 編集 - - - - - {#each workBookTasksForTable as task, index} - - - -
- - {index + 1} -
-
- - - -
- -
-
- - - - +
List of workbook tasks with their grades and comments
+ + + # + + グレード + + 問題名 + + + + 編集 + + + + + {#each workBookTasksForTable as task, index} + + + +
+ + {index + 1} +
+
+ + + +
+ +
+
+ + + + + + + + - - - - - - - -
- - - -
List of workbook tasks with their grades and comments
+ + {task.comment || placeholderForComment} + + + + + + + + + {/each} + + +
{/if}