Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .coderabbit.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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: |
Expand Down
6 changes: 3 additions & 3 deletions docs/guides/agent-rules/testing-e2e.md
Original file line number Diff line number Diff line change
Expand Up @@ -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';
```
Expand Down
12 changes: 6 additions & 6 deletions docs/guides/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -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';
```

Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,8 @@
},
"type": "module",
"imports": {
"#lib/*": "./src/lib/*"
"#lib/*": "./src/lib/*",
"#features/*": "./src/features/*"
},
"dependencies": {
"@dnd-kit/helpers": "0.5.1",
Expand Down
2 changes: 1 addition & 1 deletion src/app.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
8 changes: 4 additions & 4 deletions src/features/tasks/components/contest-table/TaskTable.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
<script lang="ts">
import type { TaskResult } from '#lib/types/task.js';
import type { VoteStatisticsMap } from '$features/votes/types/vote_result';
import type { VoteStatisticsMap } from '#features/votes/types/vote_result.js';

import VotableGrade from '@/features/votes/components/VotableGrade.svelte';
import VotableGrade from '#features/votes/components/VotableGrade.svelte';
import ExternalLinkWrapper from '#lib/components/ExternalLinkWrapper.svelte';
import UpdatingDropdown from '#lib/components/SubmissionStatus/UpdatingDropdown.svelte';

import { getTaskUrl, removeTaskIndexFromTitle } from '#lib/utils/task.js';
import { formatAojTitle } from '$features/tasks/utils/contest-table/aoj_labels';
import { formatAojTitle } from '#features/tasks/utils/contest-table/aoj_labels.js';

interface Props {
taskResult: TaskResult;
Expand Down
4 changes: 2 additions & 2 deletions src/features/tasks/stores/active_contest_type.svelte.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@

import { describe, test, expect, vi, beforeEach, afterEach } from 'vitest';

import type { ContestTableProviderGroups } from '$features/tasks/utils/contest-table/contest_table_provider';
import type { ContestTableProviderGroups } from '#features/tasks/utils/contest-table/contest_table_provider.js';
import {
activeContestTypeStore,
ActiveContestTypeStore,
} from '$features/tasks/stores/active_contest_type.svelte';
} from '#features/tasks/stores/active_contest_type.svelte.js';

// WHY: a single dynamic mock is the only way to toggle `browser` per describe. Every vi.mock is
// hoisted and the last registration for a module wins, so a second vi.mock of `$app/environment`
Expand Down
2 changes: 1 addition & 1 deletion src/features/tasks/stores/active_contest_type.svelte.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { useLocalStorage } from '#lib/stores/local_storage_helper.svelte.js';
import {
type ContestTableProviderGroups,
contestTableProviderGroups,
} from '$features/tasks/utils/contest-table/contest_table_provider';
} from '#features/tasks/utils/contest-table/contest_table_provider.js';

/**
* Store that manages the active contest type selection.
Expand Down
2 changes: 1 addition & 1 deletion src/features/tasks/utils/contest-table/aalc_provider.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import {
type ContestTableMetaData,
type ContestTableDisplayConfig,
} from '$features/tasks/types/contest-table/contest_table_provider';
} from '#features/tasks/types/contest-table/contest_table_provider.js';
import type { TaskResult } from '#lib/types/task.js';

import { classifyContest } from '#lib/contests/index.js';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {
ABC001ToABC041Provider,
} from './abc_providers';
import { parseContestRound } from './contest_table_provider_base';
import { taskResultsForContestTableProvider } from '$features/tasks/fixtures/contest-table/contest_table_provider';
import { taskResultsForContestTableProvider } from '#features/tasks/fixtures/contest-table/contest_table_provider.js';

describe('ABC providers', () => {
const mockTaskResults: TaskResults = taskResultsForContestTableProvider;
Expand Down
2 changes: 1 addition & 1 deletion src/features/tasks/utils/contest-table/abc_providers.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import {
type ContestTableMetaData,
type ContestTableDisplayConfig,
} from '$features/tasks/types/contest-table/contest_table_provider';
} from '#features/tasks/types/contest-table/contest_table_provider.js';
import type { TaskResult } from '#lib/types/task.js';

import { classifyContest, getContestNameLabel } from '#lib/contests/index.js';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { ContestType } from '#lib/contests/types/contest.js';
import type { TaskResults } from '#lib/types/task.js';

import { ABSProvider } from './abs_provider';
import { taskResultsForABS } from '$features/tasks/fixtures/contest-table/contest_table_provider';
import { taskResultsForABS } from '#features/tasks/fixtures/contest-table/contest_table_provider.js';

describe('ABSProvider', () => {
describe('ABS', () => {
Expand Down
2 changes: 1 addition & 1 deletion src/features/tasks/utils/contest-table/abs_provider.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import {
type ContestTableMetaData,
type ContestTableDisplayConfig,
} from '$features/tasks/types/contest-table/contest_table_provider';
} from '#features/tasks/types/contest-table/contest_table_provider.js';
import type { TaskResult } from '#lib/types/task.js';

import { classifyContest } from '#lib/contests/index.js';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
taskResultsForACLBeginnerProvider,
taskResultsForACLProvider,
taskResultsForContestTableProvider,
} from '$features/tasks/fixtures/contest-table/contest_table_provider';
} from '#features/tasks/fixtures/contest-table/contest_table_provider.js';

describe('ACLPracticeProvider', () => {
test('expects to filter tasks with contest_id "practice2"', () => {
Expand Down
2 changes: 1 addition & 1 deletion src/features/tasks/utils/contest-table/acl_providers.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import {
type ContestTableMetaData,
type ContestTableDisplayConfig,
} from '$features/tasks/types/contest-table/contest_table_provider';
} from '#features/tasks/types/contest-table/contest_table_provider.js';
import type { TaskResult } from '#lib/types/task.js';

import { classifyContest } from '#lib/contests/index.js';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import type { TaskResults } from '#lib/types/task.js';

import { AGC001OnwardsProvider } from './agc_provider';
import { parseContestRound } from './contest_table_provider_base';
import { taskResultsForAGC001OnwardsProvider } from '$features/tasks/fixtures/contest-table/contest_table_provider';
import { taskResultsForAGC001OnwardsProvider } from '#features/tasks/fixtures/contest-table/contest_table_provider.js';

describe('AGC001OnwardsProvider', () => {
test('expects to filter tasks to include only AGC001 and later', () => {
Expand Down
2 changes: 1 addition & 1 deletion src/features/tasks/utils/contest-table/agc_provider.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { type ContestTableMetaData } from '$features/tasks/types/contest-table/contest_table_provider';
import { type ContestTableMetaData } from '#features/tasks/types/contest-table/contest_table_provider.js';
import type { TaskResult } from '#lib/types/task.js';

import { classifyContest, getContestNameLabel } from '#lib/contests/index.js';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import type { TaskResult, TaskResults } from '#lib/types/task.js';
import {
type ContestTableMetaData,
type ContestTableDisplayConfig,
} from '$features/tasks/types/contest-table/contest_table_provider';
} from '#features/tasks/types/contest-table/contest_table_provider.js';

import { ContestTableProviderBase } from './contest_table_provider_base';
import {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import type { TaskResult, TaskResults } from '#lib/types/task.js';
import {
type ContestTableMetaData,
type ContestTableDisplayConfig,
} from '$features/tasks/types/contest-table/contest_table_provider';
} from '#features/tasks/types/contest-table/contest_table_provider.js';

import { ContestTableProviderBase } from './contest_table_provider_base';
import {
Expand Down
2 changes: 1 addition & 1 deletion src/features/tasks/utils/contest-table/aoj_labels.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import type { TaskResults } from '#lib/types/task.js';
import type {
ContestTableDisplayConfig,
ContestTableTitleStyle,
} from '$features/tasks/types/contest-table/contest_table_provider';
} from '#features/tasks/types/contest-table/contest_table_provider.js';

// contest_id -> (task_table_index -> letter). Used only for years with judge gaps.
// Keyed by full contest_id so AOJ contests (ICPC, JAG) can share one map.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
ARC001ToARC057Provider,
} from './arc_providers';
import { parseContestRound } from './contest_table_provider_base';
import { taskResultsForARC104OnwardsProvider } from '$features/tasks/fixtures/contest-table/contest_table_provider';
import { taskResultsForARC104OnwardsProvider } from '#features/tasks/fixtures/contest-table/contest_table_provider.js';

describe('ARC providers', () => {
// ARC 104 Onwards only
Expand Down
2 changes: 1 addition & 1 deletion src/features/tasks/utils/contest-table/arc_providers.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import {
type ContestTableMetaData,
type ContestTableDisplayConfig,
} from '$features/tasks/types/contest-table/contest_table_provider';
} from '#features/tasks/types/contest-table/contest_table_provider.js';
import type { TaskResult } from '#lib/types/task.js';

import { classifyContest, getContestNameLabel } from '#lib/contests/index.js';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {
taskResultsForAWC0101To0149Provider,
taskResultsForAWC0150Provider,
taskResultsForAWC0151OnwardsProvider,
} from '$features/tasks/fixtures/contest-table/contest_table_provider';
} from '#features/tasks/fixtures/contest-table/contest_table_provider.js';

const allAWCTaskResults: TaskResults = [
...taskResultsForAWC0001To0099Provider,
Expand Down
2 changes: 1 addition & 1 deletion src/features/tasks/utils/contest-table/awc_provider.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import {
type ContestTableMetaData,
type ContestTableDisplayConfig,
} from '$features/tasks/types/contest-table/contest_table_provider';
} from '#features/tasks/types/contest-table/contest_table_provider.js';
import { ContestType } from '#lib/contests/types/contest.js';
import type { TaskResult } from '#lib/types/task.js';

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { ContestType } from '#lib/contests/types/contest.js';
import type { TaskResults } from '#lib/types/task.js';

import { ABCLikeProvider } from './axc_like_provider';
import { taskResultsForABCLikeProvider } from '$features/tasks/fixtures/contest-table/contest_table_provider';
import { taskResultsForABCLikeProvider } from '#features/tasks/fixtures/contest-table/contest_table_provider.js';

describe('ABCLikeProvider', () => {
test('expects to filter tasks to include only ABC-Like contests', () => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import {
type ContestTableMetaData,
type ContestTableDisplayConfig,
} from '$features/tasks/types/contest-table/contest_table_provider';
} from '#features/tasks/types/contest-table/contest_table_provider.js';
import type { TaskResult } from '#lib/types/task.js';

import { classifyContest } from '#lib/contests/index.js';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { describe, test, expect } from 'vitest';
import { ContestType } from '#lib/contests/types/contest.js';

import { ABSProvider } from './contest_table_provider';
import { taskResultsForABS } from '$features/tasks/fixtures/contest-table/contest_table_provider';
import { taskResultsForABS } from '#features/tasks/fixtures/contest-table/contest_table_provider.js';

describe('ContestTableProviderBase', () => {
describe('Common provider functionality', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import {
type ContestTableMetaData,
type ContestTableDisplayConfig,
type ProviderKey,
} from '$features/tasks/types/contest-table/contest_table_provider';
} from '#features/tasks/types/contest-table/contest_table_provider.js';
import { ContestType } from '#lib/contests/types/contest.js';
import type { TaskResults, TaskResult } from '#lib/types/task.js';

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {
ContestTableProviderBase,
ContestTableProviderGroup,
} from './contest_table_provider';
import { TESSOKU_SECTIONS } from '$features/tasks/types/contest-table/contest_table_provider';
import { TESSOKU_SECTIONS } from '#features/tasks/types/contest-table/contest_table_provider.js';
import { EDPCProvider, TDPCProvider } from './dp_providers';
import {
TessokuBookForExamplesProvider,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { type ContestTablesMetaData } from '$features/tasks/types/contest-table/contest_table_provider';
import { type ContestTablesMetaData } from '#features/tasks/types/contest-table/contest_table_provider.js';
import { ContestType } from '#lib/contests/types/contest.js';

import { ContestTableProviderBase } from './contest_table_provider_base';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { describe, test, expect } from 'vitest';

import { ContestType } from '#lib/contests/types/contest.js';

import { TESSOKU_SECTIONS } from '$features/tasks/types/contest-table/contest_table_provider';
import { TESSOKU_SECTIONS } from '#features/tasks/types/contest-table/contest_table_provider.js';

import {
ICPC_PRELIM_OLDEST_YEAR,
Expand Down
2 changes: 1 addition & 1 deletion src/features/tasks/utils/contest-table/dp_providers.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import {
type ContestTableMetaData,
type ContestTableDisplayConfig,
} from '$features/tasks/types/contest-table/contest_table_provider';
} from '#features/tasks/types/contest-table/contest_table_provider.js';
import type { TaskResult } from '#lib/types/task.js';

import { classifyContest } from '#lib/contests/index.js';
Expand Down
2 changes: 1 addition & 1 deletion src/features/tasks/utils/contest-table/fps24_provider.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import {
type ContestTableMetaData,
type ContestTableDisplayConfig,
} from '$features/tasks/types/contest-table/contest_table_provider';
} from '#features/tasks/types/contest-table/contest_table_provider.js';
import type { TaskResult } from '#lib/types/task.js';

import { classifyContest } from '#lib/contests/index.js';
Expand Down
2 changes: 1 addition & 1 deletion src/features/tasks/utils/contest-table/game24_provider.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import {
type ContestTableMetaData,
type ContestTableDisplayConfig,
} from '$features/tasks/types/contest-table/contest_table_provider';
} from '#features/tasks/types/contest-table/contest_table_provider.js';
import type { TaskResult } from '#lib/types/task.js';

import { classifyContest } from '#lib/contests/index.js';
Expand Down
2 changes: 1 addition & 1 deletion src/features/tasks/utils/contest-table/joi_providers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import {
type ContestTableDisplayConfig,
JOI_SECOND_QUAL_ROUND_SECTIONS,
JOI_FINAL_ROUND_SECTIONS,
} from '$features/tasks/types/contest-table/contest_table_provider';
} from '#features/tasks/types/contest-table/contest_table_provider.js';
import { ContestType } from '#lib/contests/types/contest.js';
import type { TaskResult } from '#lib/types/task.js';

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import {
type ContestTableMetaData,
type ContestTableDisplayConfig,
} from '$features/tasks/types/contest-table/contest_table_provider';
} from '#features/tasks/types/contest-table/contest_table_provider.js';
import type { TaskResult } from '#lib/types/task.js';

import { classifyContest } from '#lib/contests/index.js';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import {
type ContestTableMetaData,
type ContestTableDisplayConfig,
TESSOKU_SECTIONS,
} from '$features/tasks/types/contest-table/contest_table_provider';
} from '#features/tasks/types/contest-table/contest_table_provider.js';
import { ContestType } from '#lib/contests/types/contest.js';
import type { TaskResult } from '#lib/types/task.js';

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { ContestType } from '#lib/contests/types/contest.js';
import type { TaskResults } from '#lib/types/task.js';

import { Typical90Provider } from './typical90_provider';
import { taskResultsForContestTableProvider } from '$features/tasks/fixtures/contest-table/contest_table_provider';
import { taskResultsForContestTableProvider } from '#features/tasks/fixtures/contest-table/contest_table_provider.js';

describe('Typical90Provider', () => {
const mockTaskResults: TaskResults = taskResultsForContestTableProvider;
Expand Down
Loading
Loading