diff --git a/.coderabbit.yaml b/.coderabbit.yaml
index e85513904..f50288895 100644
--- a/.coderabbit.yaml
+++ b/.coderabbit.yaml
@@ -25,7 +25,7 @@ reviews:
- path: 'src/lib/server/**'
instructions: |
- This directory is server-only. Never import from client-side code or Svelte components.
- - DB client must be accessed only via `$lib/server/database`.
+ - DB client must be accessed only via `#lib/server/database.js`.
# --- Schema / validation ---
- path: 'src/**/zod/**'
instructions: |
diff --git a/docs/guides/agent-rules/testing-e2e.md b/docs/guides/agent-rules/testing-e2e.md
index 1c38e55ae..71e826e21 100644
--- a/docs/guides/agent-rules/testing-e2e.md
+++ b/docs/guides/agent-rules/testing-e2e.md
@@ -10,14 +10,14 @@ paths:
## Setup
- File extension: **must** be `.spec.ts` (`.test.ts` not detected by `playwright.config.ts`)
-- No path aliases or subpath imports (`$features` is resolved only by Vite; `#lib/*.js` resolves to `src/lib/*.js`, but the Playwright runner does not map it to the `.ts` source). Use type-only imports for types:
+- No runtime imports through subpath imports (`#lib/*.js` and `#features/*.js` resolve to `src/**/*.js`, but the Playwright runner does not map them to the `.ts` source). Use relative imports, or type-only imports for types:
```typescript
// Bad: runtime import fails in E2E
-import { TAB_SOLUTION } from '$features/workbooks/types/workbook';
+import { TAB_SOLUTION } from '#features/workbooks/types/workbook.js';
// Good: type-only import + local constant
-import type { WorkBookTab } from '$features/workbooks/types/workbook';
+import type { WorkBookTab } from '#features/workbooks/types/workbook.js';
const TAB_SOLUTION: WorkBookTab = 'solution';
```
diff --git a/docs/guides/architecture.md b/docs/guides/architecture.md
index 9f35fb9b5..4b7f08bcd 100644
--- a/docs/guides/architecture.md
+++ b/docs/guides/architecture.md
@@ -139,16 +139,16 @@ include: [
];
```
-### 6. path alias と subpath imports
+### 6. subpath imports(`#lib`・`#features`)
-`src/lib` は `package.json#imports` の `#lib/*` で参照する。SvelteKit 3 で組み込みの `$lib` が削除されるため `$lib` は使わず、oxlint と ESLint の `no-restricted-imports` で禁止している。
-`#lib` には明示的な拡張子が必要で、TS モジュールは `.js`、コンポーネントは `.svelte`、runes モジュール(`.svelte.ts`)は `.svelte.js` と書く。`vi.mock` の specifier も同じ形にする。
-lint が検出するのは import・export 宣言(`import type`・`export … from` を含む)と `.ts` の動的 `import()` で、`vi.mock('$lib/...')` と `.svelte` 内の動的 `import()` は検出しない。import の specifier をまとめて変えたときは、`rg -n '\$lib' src e2e prisma` で残りが無いことも確かめる。
-`$features` は `svelte.config.js` の alias で定義している。
+`src/lib` は `package.json#imports` の `#lib/*`、`src/features` は `#features/*` で参照する。SvelteKit 3 は組み込みの `$lib` を削除し、`kit.alias` も非推奨にするため、`svelte.config.js` に alias は置かない。
+拡張子は明示する。TS モジュールは `.js`、コンポーネントは `.svelte`、runes モジュール(`.svelte.ts`)は `.svelte.js` と書く。`vi.mock` の specifier も同じ形にする。
+`$lib` は Kit 2 が組み込みで解決してしまうため、oxlint と ESLint の `no-restricted-imports` で禁止している。lint が検出するのは import・export 宣言(`import type`・`export … from` を含む)と `.ts` の動的 `import()` で、`vi.mock('$lib/...')` と `.svelte` 内の動的 `import()` は検出しない。`$features` と `@/` は alias が無いので、import すると型チェックで失敗する。
+import の specifier をまとめて変えたときは、`rg -n '\$lib|\$features|@/' src e2e prisma` で残りが無いことも確かめる。
```typescript
import { formatDate } from '#lib/utils/format.js';
-import WorkBookForm from '$features/workbooks/components/shared/WorkBookForm.svelte';
+import WorkBookForm from '#features/workbooks/components/shared/WorkBookForm.svelte';
import type { PageServerLoad } from './$types';
```
diff --git a/package.json b/package.json
index d16d1b94c..4ee8d36dd 100644
--- a/package.json
+++ b/package.json
@@ -71,7 +71,8 @@
},
"type": "module",
"imports": {
- "#lib/*": "./src/lib/*"
+ "#lib/*": "./src/lib/*",
+ "#features/*": "./src/features/*"
},
"dependencies": {
"@dnd-kit/helpers": "0.5.1",
diff --git a/src/app.d.ts b/src/app.d.ts
index ec0f9a009..30b104fb9 100644
--- a/src/app.d.ts
+++ b/src/app.d.ts
@@ -7,7 +7,7 @@ declare global {
namespace App {
// interface Error {}
interface Locals {
- auth: import('$features/auth/server/auth').AuthRequest;
+ auth: import('#features/auth/server/auth.js').AuthRequest;
user: {
id: string;
name: string;
diff --git a/src/features/tasks/components/contest-table/TaskTable.svelte b/src/features/tasks/components/contest-table/TaskTable.svelte
index ab635fa87..302492f9b 100644
--- a/src/features/tasks/components/contest-table/TaskTable.svelte
+++ b/src/features/tasks/components/contest-table/TaskTable.svelte
@@ -18,18 +18,18 @@
ContestTableProvider,
ContestTableDisplayConfig,
ContestTableMetaData,
- } from '$features/tasks/types/contest-table/contest_table_provider';
+ } from '#features/tasks/types/contest-table/contest_table_provider.js';
import type { ContestTaskPairKey } from '#lib/types/contest_task_pair.js';
- import type { VoteStatisticsMap } from '$features/votes/types/vote_result';
+ import type { VoteStatisticsMap } from '#features/votes/types/vote_result.js';
import TaskTableBodyCell from './TaskTableBodyCell.svelte';
- import { activeContestTypeStore } from '$features/tasks/stores/active_contest_type.svelte';
+ import { activeContestTypeStore } from '#features/tasks/stores/active_contest_type.svelte.js';
import {
contestTableProviderGroups,
type ContestTableProviderGroup,
type ContestTableProviderGroups,
- } from '$features/tasks/utils/contest-table/contest_table_provider';
+ } from '#features/tasks/utils/contest-table/contest_table_provider.js';
import { useLocalStorage } from '#lib/stores/local_storage_helper.svelte.js';
diff --git a/src/features/tasks/components/contest-table/TaskTableBodyCell.svelte b/src/features/tasks/components/contest-table/TaskTableBodyCell.svelte
index 47197ed3d..af899be68 100644
--- a/src/features/tasks/components/contest-table/TaskTableBodyCell.svelte
+++ b/src/features/tasks/components/contest-table/TaskTableBodyCell.svelte
@@ -1,13 +1,13 @@
diff --git a/src/features/workbooks/components/list/SolutionWorkBookList.svelte b/src/features/workbooks/components/list/SolutionWorkBookList.svelte
index d82d1a21f..64af06090 100644
--- a/src/features/workbooks/components/list/SolutionWorkBookList.svelte
+++ b/src/features/workbooks/components/list/SolutionWorkBookList.svelte
@@ -3,24 +3,24 @@
import type { Roles } from '#lib/types/user.js';
import type { TaskResults } from '#lib/types/task.js';
- import type { WorkbooksList } from '$features/workbooks/types/workbook';
+ import type { WorkbooksList } from '#features/workbooks/types/workbook.js';
import {
SolutionCategory,
type SolutionCategories,
SOLUTION_LABELS,
ALL_SOLUTION_CATEGORIES,
type SelectedSolutionCategory,
- } from '$features/workbooks/types/workbook_placement';
+ } from '#features/workbooks/types/workbook_placement.js';
- import { countReadableWorkbooks } from '$features/workbooks/utils/workbooks';
+ import { countReadableWorkbooks } from '#features/workbooks/utils/workbooks.js';
import {
groupBySolutionCategory,
filterGroupsByRole,
type WorkbookGroup,
- } from '$features/workbooks/utils/solution_category_group';
+ } from '#features/workbooks/utils/solution_category_group.js';
- import SolutionTable from '$features/workbooks/components/list/SolutionTable.svelte';
- import EmptyWorkbookList from '$features/workbooks/components/list/EmptyWorkbookList.svelte';
+ import SolutionTable from '#features/workbooks/components/list/SolutionTable.svelte';
+ import EmptyWorkbookList from '#features/workbooks/components/list/EmptyWorkbookList.svelte';
interface Props {
workbooks: WorkbooksList;
diff --git a/src/features/workbooks/components/list/TitleTableBodyCell.svelte b/src/features/workbooks/components/list/TitleTableBodyCell.svelte
index f2a747913..cedbba6eb 100644
--- a/src/features/workbooks/components/list/TitleTableBodyCell.svelte
+++ b/src/features/workbooks/components/list/TitleTableBodyCell.svelte
@@ -3,10 +3,10 @@
import { TableBodyCell } from 'flowbite-svelte';
- import type { WorkbookList } from '$features/workbooks/types/workbook';
+ import type { WorkbookList } from '#features/workbooks/types/workbook.js';
- import { getUrlSlugFrom } from '$features/workbooks/utils/workbooks';
- import PublicationStatusLabel from '$features/workbooks/components/shared/PublicationStatusLabel.svelte';
+ import { getUrlSlugFrom } from '#features/workbooks/utils/workbooks.js';
+ import PublicationStatusLabel from '#features/workbooks/components/shared/PublicationStatusLabel.svelte';
interface Props {
paddingLeft?: string;
diff --git a/src/features/workbooks/components/list/WorkBookList.svelte b/src/features/workbooks/components/list/WorkBookList.svelte
index 6012ed0a5..e728e6d09 100644
--- a/src/features/workbooks/components/list/WorkBookList.svelte
+++ b/src/features/workbooks/components/list/WorkBookList.svelte
@@ -1,16 +1,16 @@