diff --git a/.gitignore b/.gitignore
index c121968..d06da34 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,9 +3,10 @@
/.pnp
.pnp.js
-# testing
+# generated reports and local captures
/coverage
/test-results/
+/output/
# next.js
/.next/
@@ -18,7 +19,6 @@
# misc
.DS_Store
*.pem
-/output/
# debug
npm-debug.log*
diff --git a/AGENTS.md b/AGENTS.md
index 885491b..6d46d5a 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -6,14 +6,15 @@
- 현재 MDX 파이프라인을 보존한다. MDX는 `next.config.mjs`의 `@mdx-js/loader`
기반 커스텀 webpack rule에 남겨둔다. 전체 콘텐츠 파이프라인을 의도적으로
마이그레이션하지 않는 한 Next.js 내장 MDX로 바꾸지 않는다.
-- 시각화 중심 UI는 `src/components/visualization/`에 둔다. 명확한
- 아키텍처 이유 없이 다른 폴더로 옮기지 않는다.
+- 시각화 중심 UI는 `shared/visualization/`에 둔다. 블로그 MDX가 재사용하는
+ 시각화 컴포넌트는 도메인 코드가 아니라 shared 시각화 모듈로 관리한다.
- `any`를 사용하지 않는다. 구체 타입을 쓰거나 `unknown`과 narrowing을
사용한다.
- `p-[13px]` 같은 arbitrary Tailwind value를 사용하지 않는다. 표준 utility,
shared token, 기존 스타일 패턴을 사용한다.
-- feature-first 구조를 flat shared folder 구조로 바꾸지 않는다.
- `src/features/`, `src/shared/`, `src/domains/`를 책임별로 유지한다.
+- DDD의 전략적 설계 개념을 차용한 domain-first modular monolith 구조를
+ 유지한다. 주요 도메인은 `src`와 같은 최상위 모듈로 분리하고, `src/app`은
+ Next.js route adapter로 제한한다.
- 블로그 콘텐츠 구조는 `posts/**/index.mdx`와 주변 `meta.json`으로 유지한다.
중첩된 series 디렉터리도 보존한다.
- 변경이 아래 ADR 작성 조건에 걸리면 반드시 ADR을 작성하거나 갱신한다.
@@ -38,16 +39,20 @@ ADR 작성 기준:
## 프로젝트 구조
-- `src/app/`: Next.js App Router 페이지, 레이아웃, 핸들러
-- `src/core/`: 앱 수준 provider와 설정 조합
-- `src/domains/`: feature 사이에서 공유되는 계약과 스키마
-- `src/features/`: blog, home, resume, search 같은 feature 모듈
-- `src/shared/`: 재사용 가능한 UI, layout, analytics, SEO, provider
-- `src/components/visualization/`: animation과 시각화 중심 컴포넌트
-- `src/styles/`: design token과 global style
+- `src/app/`: Next.js App Router route adapter, route handler, metadata entry
+- `blog/`: 글 도메인. post schema, repository, publication policy, series,
+ blog UI, view-count use case
+- `resume/`: 이력서 도메인. resume data, ordering, resume UI
+- `search/`: 검색 도메인. command palette, search action, recommendation
+- `site/`: 도메인 조합 layer. home, AppShell, navigation, provider, site config
+- `platform/`: 외부/런타임 인프라. Supabase, Umami analytics, SEO helper,
+ devtool integration
+- `shared/`: 도메인 지식 없는 UI primitive, motion helper, testing helper,
+ visualization widget
+- `styles/`: design token과 global style
- `posts/`: 중첩 가능한 `index.mdx`와 `meta.json` 기반 블로그 콘텐츠
- `tests/`: Playwright E2E 테스트
-- `internal/`: script와 tool configuration
+- `tooling/`: script와 tool configuration
## 개발 명령
diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md
index 5ebaeeb..4cfb7ea 100644
--- a/ARCHITECTURE.md
+++ b/ARCHITECTURE.md
@@ -1,6 +1,6 @@
# ARCHITECTURE
-Last updated: 2026-05-06
+Last updated: 2026-05-07
## System Summary
@@ -21,7 +21,7 @@ Last updated: 2026-05-06
- Rendering mix:
- Static prerendered route params via `generateStaticParams` for post pages.
- Server route handlers for feed and OG image.
- - Server actions for view counting.
+ - Domain-owned server actions for view counting.
### Edge Runtime
@@ -40,12 +40,12 @@ Last updated: 2026-05-06
- Route handlers:
- `/feed.xml` (`src/app/feed.xml/route.ts`)
- `/api/og` (`src/app/api/og/route.tsx`)
-- Server action:
- - `src/app/actions/view.ts` for increment/read view count.
+- `src/app` imports page adapters from top-level domain modules and does not own
+ domain policy.
-### Content Layer (`posts/**` + `src/features/blog/services`)
+### Content Layer (`posts/**` + `blog/services`)
-- `src/features/blog/services/post-repository.ts` recursively discovers valid post folders (`index.mdx` + `meta.json`).
+- `blog/services/post-repository.ts` recursively discovers valid post folders (`index.mdx` + `meta.json`).
- `meta.json` is validated with Zod (`FeedFrontmatterSchema`).
- MDX is loaded by dynamic import per folder path.
- Reading time is auto-derived from MDX when metadata omits it.
@@ -57,22 +57,23 @@ Last updated: 2026-05-06
- `remark-gfm`
- `rehype-slug`
- `rehype-pretty-code`
-- `src/features/blog/services/markdown-parser.ts` parses MDX headings for TOC data.
-- `src/features/blog/ui/mdx/components.tsx` maps MDX nodes to UI components and interactive visualization widgets.
+- `blog/services/markdown-parser.ts` parses MDX headings for TOC data.
+- `blog/ui/mdx/components.tsx` maps MDX nodes to UI components and interactive visualization widgets.
-### Feature/Shared Layer (`src/features` + `src/shared` + `src/styles`)
+### Domain-first Modular Monolith
-- Feature-first organization:
- - `src/features/blog`, `src/features/resume`, `src/features/search`, `src/features/home`
- - `src/shared/analytics`, `src/shared/layout`, `src/shared/ui`, `src/shared/providers`, `src/shared/seo`
- - `src/components/visualization` is intentionally preserved for heavy visualization widgets.
-- Design tokens in `src/styles/tokens.css`.
-- Global base styles and typography in `src/styles/globals.css`.
-- Theming via `next-themes` provider.
+- `blog/`: post schema, repository, publication policy, series, blog UI, view-count use case.
+- `resume/`: resume data, ordering, resume UI.
+- `search/`: command palette, search action, search recommendation.
+- `site/`: home composition, AppShell, navigation, providers, site config.
+- `platform/`: Supabase integration, Umami analytics, SEO helper, devtools.
+- `shared/`: domain-agnostic UI, layout primitive, motion helper, testing helper, visualization widget.
+- `styles/`: design tokens, global base styles, local font CSS.
+- Theming via `next-themes` provider in `site/providers`.
### Data and Integrations
-- Supabase client setup in `src/shared/integrations/supabase.ts`.
+- Supabase client setup in `platform/integrations/supabase.ts`.
- View count data model:
- table: `public.views`
- rpc: `increment_view(slug_input text) -> bigint`
@@ -80,8 +81,8 @@ Last updated: 2026-05-06
### Analytics and SEO
-- Umami event helpers in `src/shared/analytics/lib/analytics.ts`.
-- Trackers in `src/shared/analytics/components/*`.
+- Umami event helpers in `platform/analytics/lib/analytics.ts`.
+- Trackers in `platform/analytics/components/*`.
- Structured data via `JsonLd` component in layout and post page.
## Request/Data Flows
@@ -92,7 +93,7 @@ Last updated: 2026-05-06
2. Request to `/blog/[slug]` resolves post via `getFeedData(slug)`.
3. MDX source is parsed for heading structure (TOC).
4. MDX component renders with mapped custom components.
-5. Client tracker records post view; server action can persist counter in Supabase.
+5. Client tracker records post view; `blog/api/view.ts` can persist counter in Supabase.
### Feed Flow
@@ -109,10 +110,11 @@ Last updated: 2026-05-06
## Testing and Quality
-- Unit/component tests: Vitest + Testing Library (`src/**/*.test.ts(x)`).
+- Unit/component tests: Vitest + Testing Library across top-level modules and `src/app`.
- E2E tests: Playwright mobile-focused projects (`tests/e2e/**/*.spec.ts`).
- Linting/formatting: ESLint, Prettier, markdownlint, cspell.
-- Coverage focus includes `src/features`, `src/shared`, `src/styles`, and selected route domains.
+- Coverage focus includes `blog`, `resume`, `search`, `site`, `platform`,
+ `shared`, `styles`, and selected route adapters.
## Current Architecture Risks
@@ -120,8 +122,8 @@ Last updated: 2026-05-06
- AGENTS and README mention older stack assumptions; package versions are newer.
2. Provider boundary drift:
- Route/layout changes can reintroduce duplicated tracker mounts if `AppProviders` is bypassed.
-3. Content/animation docs drift:
- - Documented `src/components/animations` path does not exist in current tree.
+3. Boundary enforcement drift:
+ - Module boundaries are physical and documented, but lint-level enforcement is not yet configured.
4. SEO endpoint mismatch risk:
- Post JSON-LD image URL differs from the actual OG route path/domain conventions.
@@ -134,3 +136,4 @@ Last updated: 2026-05-06
3. Keep token-first styling and avoid one-off visual constants where possible.
4. Keep route-level separation for feed, OG, and view-count concerns.
5. Keep Umami analytics separate from Supabase-backed public view counts.
+6. Keep top-level domain modules as documented in ADR 0011.
diff --git a/README.md b/README.md
index 82262b2..7f1c22e 100644
--- a/README.md
+++ b/README.md
@@ -75,23 +75,19 @@
```text
eunu.log/
├── 📁 src/
-│ ├── 📁 app/ # 라우트 엔트리 전용 (Next App Router)
-│ ├── 📁 core/ # 앱 전역 설정/프로바이더 조합
-│ ├── 📁 domains/ # 도메인 계약/타입/스키마
-│ ├── 📁 features/ # 기능 모듈(ui/model/services)
-│ │ ├── 📁 blog/
-│ │ ├── 📁 resume/
-│ │ ├── 📁 search/
-│ │ └── 📁 home/
-│ ├── 📁 shared/ # 공용 모듈(analytics/integrations/layout/seo/testing/ui/types)
-│ ├── 📁 components/
-│ │ └── 📁 visualization/ # 인터랙티브 알고리즘 시각화 전용
-│ └── 📁 styles/ # 전역 스타일과 토큰
+│ └── 📁 app/ # Next.js route adapter
+├── 📁 blog/ # 글 도메인
+├── 📁 resume/ # 이력서 도메인
+├── 📁 search/ # 검색 도메인
+├── 📁 site/ # 홈, AppShell, provider, site config
+├── 📁 platform/ # Supabase, Umami, SEO, devtool integration
+├── 📁 shared/ # 도메인 지식 없는 UI/모션/테스트/시각화
+├── 📁 styles/ # 전역 스타일과 토큰
├── 📁 tests/
│ └── 📁 e2e/ # Playwright E2E 테스트
-├── 📁 internal/
-│ ├── 📁 config/ # 내부 lint/spell 설정
-│ └── 📁 scripts/ # 내부 자동화/유틸 스크립트
+├── 📁 tooling/
+│ ├── 📁 config/ # lint/spell 설정
+│ └── 📁 scripts/ # 자동화/유틸 스크립트
├── 📁 posts/ # 블로그 글(MDX + 메타데이터)
│ └── 📁 [slug]/ # 글 단위 폴더
│ ├── index.mdx # 글 본문
diff --git a/src/app/actions/view.test.ts b/blog/api/view.test.ts
similarity index 98%
rename from src/app/actions/view.test.ts
rename to blog/api/view.test.ts
index 3e72155..11cb5c1 100644
--- a/src/app/actions/view.test.ts
+++ b/blog/api/view.test.ts
@@ -1,6 +1,6 @@
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { cookies, headers } from 'next/headers';
-import { getSupabaseServerClient } from '@/shared/integrations/supabase';
+import { getSupabaseServerClient } from '@/platform/integrations/supabase';
import {
getPopularViewsInRecentDays,
getViewCount,
@@ -8,7 +8,7 @@ import {
trackView,
} from './view';
-vi.mock('@/shared/integrations/supabase', () => ({
+vi.mock('@/platform/integrations/supabase', () => ({
getSupabaseServerClient: vi.fn(),
}));
diff --git a/src/app/actions/view.ts b/blog/api/view.ts
similarity index 99%
rename from src/app/actions/view.ts
rename to blog/api/view.ts
index eade91c..642ca0b 100644
--- a/src/app/actions/view.ts
+++ b/blog/api/view.ts
@@ -2,7 +2,7 @@
import { createHash, randomUUID } from 'node:crypto';
import { cookies, headers } from 'next/headers';
-import { getSupabaseServerClient } from '@/shared/integrations/supabase';
+import { getSupabaseServerClient } from '@/platform/integrations/supabase';
const VIEW_DEDUPE_WINDOW_SECONDS = 60 * 60 * 24;
const VIEW_FINGERPRINT_SALT =
diff --git a/blog/index.ts b/blog/index.ts
new file mode 100644
index 0000000..4e68f59
--- /dev/null
+++ b/blog/index.ts
@@ -0,0 +1,33 @@
+export {
+ calculateReadingTime,
+ getAllFeedSlugs,
+ getFeedData,
+ getFolderSlug,
+ getSeriesPosts,
+ getSortedFeedData,
+ type FeedQueryOptions,
+ type TocItem,
+} from './services/post-repository';
+export {
+ formatSeriesDate,
+ getSeriesGroups,
+ getSeriesSummaries,
+ type SeriesGroup,
+ type SeriesSummary,
+} from './model/series-group';
+export {
+ getPopularViewsInRecentDays,
+ getViewCount,
+ incrementView,
+ trackView,
+ type PopularViewEntry,
+} from './api/view';
+export type {
+ Feed,
+ FeedData,
+ FeedFrontmatter,
+ PostCategory,
+ PostVisibility,
+ QualityReview,
+ QualityScore,
+} from './model/types';
diff --git a/src/domains/post/model/frontmatter-schema.test.ts b/blog/model/frontmatter-schema.test.ts
similarity index 100%
rename from src/domains/post/model/frontmatter-schema.test.ts
rename to blog/model/frontmatter-schema.test.ts
diff --git a/src/domains/post/model/frontmatter-schema.ts b/blog/model/frontmatter-schema.ts
similarity index 100%
rename from src/domains/post/model/frontmatter-schema.ts
rename to blog/model/frontmatter-schema.ts
diff --git a/src/features/blog/model/series-group.test.ts b/blog/model/series-group.test.ts
similarity index 97%
rename from src/features/blog/model/series-group.test.ts
rename to blog/model/series-group.test.ts
index 5a8ff44..bf63969 100644
--- a/src/features/blog/model/series-group.test.ts
+++ b/blog/model/series-group.test.ts
@@ -1,5 +1,5 @@
import { describe, expect, it } from 'vitest';
-import type { FeedData } from '@/domains/post/model/types';
+import type { FeedData } from '@/blog/model/types';
import { getSeriesGroups, getSeriesSummaries } from './series-group';
const posts: FeedData[] = [
diff --git a/src/features/blog/model/series-group.ts b/blog/model/series-group.ts
similarity index 97%
rename from src/features/blog/model/series-group.ts
rename to blog/model/series-group.ts
index bd9fb5f..24665de 100644
--- a/src/features/blog/model/series-group.ts
+++ b/blog/model/series-group.ts
@@ -1,4 +1,4 @@
-import type { FeedData } from '@/domains/post/model/types';
+import type { FeedData } from '@/blog/model/types';
export interface SeriesGroup {
id: string;
diff --git a/src/domains/post/model/types.ts b/blog/model/types.ts
similarity index 100%
rename from src/domains/post/model/types.ts
rename to blog/model/types.ts
diff --git a/src/features/blog/services/markdown-parser.test.ts b/blog/services/markdown-parser.test.ts
similarity index 100%
rename from src/features/blog/services/markdown-parser.test.ts
rename to blog/services/markdown-parser.test.ts
diff --git a/src/features/blog/services/markdown-parser.ts b/blog/services/markdown-parser.ts
similarity index 96%
rename from src/features/blog/services/markdown-parser.ts
rename to blog/services/markdown-parser.ts
index 5e5e02c..b43a0cf 100644
--- a/src/features/blog/services/markdown-parser.ts
+++ b/blog/services/markdown-parser.ts
@@ -1,4 +1,4 @@
-import { getFolderSlug, type TocItem } from '@/features/blog/services/post-repository';
+import { getFolderSlug, type TocItem } from '@/blog/services/post-repository';
import fs from 'fs';
import path from 'path';
diff --git a/src/features/blog/services/post-repository.test.ts b/blog/services/post-repository.test.ts
similarity index 100%
rename from src/features/blog/services/post-repository.test.ts
rename to blog/services/post-repository.test.ts
diff --git a/src/features/blog/services/post-repository.ts b/blog/services/post-repository.ts
similarity index 98%
rename from src/features/blog/services/post-repository.ts
rename to blog/services/post-repository.ts
index 18be5c1..8aea2ec 100644
--- a/src/features/blog/services/post-repository.ts
+++ b/blog/services/post-repository.ts
@@ -1,7 +1,7 @@
import fs from 'fs';
import path from 'path';
-import type { FeedData, Feed, FeedFrontmatter } from '@/domains/post/model/types';
-import { FeedFrontmatterSchema } from '@/domains/post/model/frontmatter-schema';
+import type { FeedData, Feed, FeedFrontmatter } from '@/blog/model/types';
+import { FeedFrontmatterSchema } from '@/blog/model/frontmatter-schema';
const postsDirectory = path.join(process.cwd(), 'posts');
const isProduction = process.env.NODE_ENV === 'production';
diff --git a/src/features/blog/services/publication-policy.test.ts b/blog/services/publication-policy.test.ts
similarity index 97%
rename from src/features/blog/services/publication-policy.test.ts
rename to blog/services/publication-policy.test.ts
index 5c968ca..39dda91 100644
--- a/src/features/blog/services/publication-policy.test.ts
+++ b/blog/services/publication-policy.test.ts
@@ -1,7 +1,7 @@
// @vitest-environment node
import { describe, expect, it } from 'vitest';
-import type { FeedData, QualityReview } from '@/domains/post/model/types';
+import type { FeedData, QualityReview } from '@/blog/model/types';
import { getAllFeedSlugs, getSortedFeedData } from './post-repository';
const FEATURED_SLUGS = [
diff --git a/src/features/blog/ui/components/CategoryFilter.test.tsx b/blog/ui/components/CategoryFilter.test.tsx
similarity index 100%
rename from src/features/blog/ui/components/CategoryFilter.test.tsx
rename to blog/ui/components/CategoryFilter.test.tsx
diff --git a/src/features/blog/ui/components/CategoryFilter/CategoryFilter.tsx b/blog/ui/components/CategoryFilter/CategoryFilter.tsx
similarity index 100%
rename from src/features/blog/ui/components/CategoryFilter/CategoryFilter.tsx
rename to blog/ui/components/CategoryFilter/CategoryFilter.tsx
diff --git a/src/features/blog/ui/components/CategoryFilter/index.ts b/blog/ui/components/CategoryFilter/index.ts
similarity index 100%
rename from src/features/blog/ui/components/CategoryFilter/index.ts
rename to blog/ui/components/CategoryFilter/index.ts
diff --git a/src/features/blog/ui/components/GiscusComments.test.tsx b/blog/ui/components/GiscusComments.test.tsx
similarity index 100%
rename from src/features/blog/ui/components/GiscusComments.test.tsx
rename to blog/ui/components/GiscusComments.test.tsx
diff --git a/src/features/blog/ui/components/GiscusComments.tsx b/blog/ui/components/GiscusComments.tsx
similarity index 100%
rename from src/features/blog/ui/components/GiscusComments.tsx
rename to blog/ui/components/GiscusComments.tsx
diff --git a/src/features/blog/ui/components/ImageGrid.test.tsx b/blog/ui/components/ImageGrid.test.tsx
similarity index 100%
rename from src/features/blog/ui/components/ImageGrid.test.tsx
rename to blog/ui/components/ImageGrid.test.tsx
diff --git a/src/features/blog/ui/components/ImageGrid.tsx b/blog/ui/components/ImageGrid.tsx
similarity index 100%
rename from src/features/blog/ui/components/ImageGrid.tsx
rename to blog/ui/components/ImageGrid.tsx
diff --git a/src/features/blog/ui/components/MermaidDiagram.test.tsx b/blog/ui/components/MermaidDiagram.test.tsx
similarity index 100%
rename from src/features/blog/ui/components/MermaidDiagram.test.tsx
rename to blog/ui/components/MermaidDiagram.test.tsx
diff --git a/src/features/blog/ui/components/MermaidDiagram.tsx b/blog/ui/components/MermaidDiagram.tsx
similarity index 100%
rename from src/features/blog/ui/components/MermaidDiagram.tsx
rename to blog/ui/components/MermaidDiagram.tsx
diff --git a/src/features/blog/ui/components/PostCard.test.tsx b/blog/ui/components/PostCard.test.tsx
similarity index 97%
rename from src/features/blog/ui/components/PostCard.test.tsx
rename to blog/ui/components/PostCard.test.tsx
index 6f78040..fef4c07 100644
--- a/src/features/blog/ui/components/PostCard.test.tsx
+++ b/blog/ui/components/PostCard.test.tsx
@@ -2,7 +2,7 @@ import { render, screen } from '@testing-library/react';
import type { ReactNode } from 'react';
import { describe, expect, it } from 'vitest';
import PostCard from './PostCard/PostCard';
-import type { FeedData } from '@/domains/post/model/types';
+import type { FeedData } from '@/blog/model/types';
vi.mock('next/link', () => ({
default: ({
diff --git a/src/features/blog/ui/components/PostCard/PostCard.tsx b/blog/ui/components/PostCard/PostCard.tsx
similarity index 99%
rename from src/features/blog/ui/components/PostCard/PostCard.tsx
rename to blog/ui/components/PostCard/PostCard.tsx
index 14b7e94..a811f94 100644
--- a/src/features/blog/ui/components/PostCard/PostCard.tsx
+++ b/blog/ui/components/PostCard/PostCard.tsx
@@ -1,6 +1,6 @@
import Link from 'next/link';
import { clsx } from 'clsx';
-import { FeedData } from '@/domains/post/model/types';
+import { FeedData } from '@/blog/model/types';
import { CategoryIcon } from '@/shared/ui/icons/AppSectionIcon';
interface PostCardProps {
diff --git a/src/features/blog/ui/components/PostCard/index.ts b/blog/ui/components/PostCard/index.ts
similarity index 100%
rename from src/features/blog/ui/components/PostCard/index.ts
rename to blog/ui/components/PostCard/index.ts
diff --git a/src/features/blog/ui/components/PostList.test.tsx b/blog/ui/components/PostList.test.tsx
similarity index 96%
rename from src/features/blog/ui/components/PostList.test.tsx
rename to blog/ui/components/PostList.test.tsx
index 991842b..9eabbca 100644
--- a/src/features/blog/ui/components/PostList.test.tsx
+++ b/blog/ui/components/PostList.test.tsx
@@ -1,7 +1,7 @@
import { describe, expect, it, vi } from 'vitest';
import { render, screen } from '@testing-library/react';
import PostList from './PostList/PostList';
-import type { FeedData } from '@/domains/post/model/types';
+import type { FeedData } from '@/blog/model/types';
vi.mock('framer-motion', () => ({
motion: {
diff --git a/src/features/blog/ui/components/PostList/PostList.tsx b/blog/ui/components/PostList/PostList.tsx
similarity index 97%
rename from src/features/blog/ui/components/PostList/PostList.tsx
rename to blog/ui/components/PostList/PostList.tsx
index f1a1579..457eb52 100644
--- a/src/features/blog/ui/components/PostList/PostList.tsx
+++ b/blog/ui/components/PostList/PostList.tsx
@@ -4,7 +4,7 @@ import { motion } from 'framer-motion';
import type { Variants } from 'framer-motion';
import { PostCard } from '../PostCard';
import { EmptyState } from '@/shared/ui';
-import type { FeedData } from '@/domains/post/model/types';
+import type { FeedData } from '@/blog/model/types';
import {
useEffectiveMotionMode,
type EffectiveMotionMode,
diff --git a/src/features/blog/ui/components/PostList/index.ts b/blog/ui/components/PostList/index.ts
similarity index 100%
rename from src/features/blog/ui/components/PostList/index.ts
rename to blog/ui/components/PostList/index.ts
diff --git a/src/features/blog/ui/components/ReadingProgress/ReadingProgress.test.tsx b/blog/ui/components/ReadingProgress/ReadingProgress.test.tsx
similarity index 100%
rename from src/features/blog/ui/components/ReadingProgress/ReadingProgress.test.tsx
rename to blog/ui/components/ReadingProgress/ReadingProgress.test.tsx
diff --git a/src/features/blog/ui/components/ReadingProgress/ReadingProgress.tsx b/blog/ui/components/ReadingProgress/ReadingProgress.tsx
similarity index 100%
rename from src/features/blog/ui/components/ReadingProgress/ReadingProgress.tsx
rename to blog/ui/components/ReadingProgress/ReadingProgress.tsx
diff --git a/src/features/blog/ui/components/ReadingProgress/index.ts b/blog/ui/components/ReadingProgress/index.ts
similarity index 100%
rename from src/features/blog/ui/components/ReadingProgress/index.ts
rename to blog/ui/components/ReadingProgress/index.ts
diff --git a/src/features/blog/ui/components/ScrollWorkflow.test.tsx b/blog/ui/components/ScrollWorkflow.test.tsx
similarity index 100%
rename from src/features/blog/ui/components/ScrollWorkflow.test.tsx
rename to blog/ui/components/ScrollWorkflow.test.tsx
diff --git a/src/features/blog/ui/components/ScrollWorkflow.tsx b/blog/ui/components/ScrollWorkflow.tsx
similarity index 100%
rename from src/features/blog/ui/components/ScrollWorkflow.tsx
rename to blog/ui/components/ScrollWorkflow.tsx
diff --git a/src/features/blog/ui/components/SeriesHubCard.test.tsx b/blog/ui/components/SeriesHubCard.test.tsx
similarity index 96%
rename from src/features/blog/ui/components/SeriesHubCard.test.tsx
rename to blog/ui/components/SeriesHubCard.test.tsx
index 5a02ee5..1067cbf 100644
--- a/src/features/blog/ui/components/SeriesHubCard.test.tsx
+++ b/blog/ui/components/SeriesHubCard.test.tsx
@@ -1,6 +1,6 @@
import { render, screen } from '@testing-library/react';
import { describe, expect, it, vi } from 'vitest';
-import type { SeriesSummary } from '@/features/blog/model/series-group';
+import type { SeriesSummary } from '@/blog/model/series-group';
import SeriesHubCard from './SeriesHubCard';
vi.mock('./SeriesTrackedLink', () => ({
diff --git a/src/features/blog/ui/components/SeriesHubCard.tsx b/blog/ui/components/SeriesHubCard.tsx
similarity index 95%
rename from src/features/blog/ui/components/SeriesHubCard.tsx
rename to blog/ui/components/SeriesHubCard.tsx
index d46ef43..f7d2aec 100644
--- a/src/features/blog/ui/components/SeriesHubCard.tsx
+++ b/blog/ui/components/SeriesHubCard.tsx
@@ -1,5 +1,5 @@
-import type { SeriesSummary } from '@/features/blog/model/series-group';
-import { formatSeriesDate } from '@/features/blog/model/series-group';
+import type { SeriesSummary } from '@/blog/model/series-group';
+import { formatSeriesDate } from '@/blog/model/series-group';
import SeriesTrackedLink from './SeriesTrackedLink';
interface SeriesHubCardProps {
diff --git a/src/features/blog/ui/components/SeriesHubList.tsx b/blog/ui/components/SeriesHubList.tsx
similarity index 86%
rename from src/features/blog/ui/components/SeriesHubList.tsx
rename to blog/ui/components/SeriesHubList.tsx
index 4670132..42e00c8 100644
--- a/src/features/blog/ui/components/SeriesHubList.tsx
+++ b/blog/ui/components/SeriesHubList.tsx
@@ -1,4 +1,4 @@
-import type { SeriesSummary } from '@/features/blog/model/series-group';
+import type { SeriesSummary } from '@/blog/model/series-group';
import SeriesHubCard from './SeriesHubCard';
interface SeriesHubListProps {
diff --git a/src/features/blog/ui/components/SeriesNavigation.test.tsx b/blog/ui/components/SeriesNavigation.test.tsx
similarity index 100%
rename from src/features/blog/ui/components/SeriesNavigation.test.tsx
rename to blog/ui/components/SeriesNavigation.test.tsx
diff --git a/src/features/blog/ui/components/SeriesNavigation/SeriesNavigation.module.css b/blog/ui/components/SeriesNavigation/SeriesNavigation.module.css
similarity index 100%
rename from src/features/blog/ui/components/SeriesNavigation/SeriesNavigation.module.css
rename to blog/ui/components/SeriesNavigation/SeriesNavigation.module.css
diff --git a/src/features/blog/ui/components/SeriesNavigation/SeriesNavigation.tsx b/blog/ui/components/SeriesNavigation/SeriesNavigation.tsx
similarity index 98%
rename from src/features/blog/ui/components/SeriesNavigation/SeriesNavigation.tsx
rename to blog/ui/components/SeriesNavigation/SeriesNavigation.tsx
index 47b72bb..200f682 100644
--- a/src/features/blog/ui/components/SeriesNavigation/SeriesNavigation.tsx
+++ b/blog/ui/components/SeriesNavigation/SeriesNavigation.tsx
@@ -2,7 +2,7 @@
import Link from 'next/link';
import { useState } from 'react';
-import { FeedData } from '@/domains/post/model/types';
+import { FeedData } from '@/blog/model/types';
import styles from './SeriesNavigation.module.css';
interface SeriesNavigationProps {
diff --git a/src/features/blog/ui/components/SeriesNavigation/index.ts b/blog/ui/components/SeriesNavigation/index.ts
similarity index 100%
rename from src/features/blog/ui/components/SeriesNavigation/index.ts
rename to blog/ui/components/SeriesNavigation/index.ts
diff --git a/src/features/blog/ui/components/SeriesTrackedLink.test.tsx b/blog/ui/components/SeriesTrackedLink.test.tsx
similarity index 97%
rename from src/features/blog/ui/components/SeriesTrackedLink.test.tsx
rename to blog/ui/components/SeriesTrackedLink.test.tsx
index eb06cd2..1ad4c5f 100644
--- a/src/features/blog/ui/components/SeriesTrackedLink.test.tsx
+++ b/blog/ui/components/SeriesTrackedLink.test.tsx
@@ -30,7 +30,7 @@ vi.mock('next/link', () => ({
},
}));
-vi.mock('@/shared/analytics/lib/analytics', () => ({
+vi.mock('@/platform/analytics/lib/analytics', () => ({
AnalyticsEvents: {
click: 'click',
},
diff --git a/src/features/blog/ui/components/SeriesTrackedLink.tsx b/blog/ui/components/SeriesTrackedLink.tsx
similarity index 93%
rename from src/features/blog/ui/components/SeriesTrackedLink.tsx
rename to blog/ui/components/SeriesTrackedLink.tsx
index 4dc8991..00694c2 100644
--- a/src/features/blog/ui/components/SeriesTrackedLink.tsx
+++ b/blog/ui/components/SeriesTrackedLink.tsx
@@ -3,7 +3,7 @@
import Link from 'next/link';
import type { MouseEvent, ReactNode } from 'react';
import { clsx } from 'clsx';
-import { AnalyticsEvents, trackEvent } from '@/shared/analytics/lib/analytics';
+import { AnalyticsEvents, trackEvent } from '@/platform/analytics/lib/analytics';
type SeriesTrackedTarget = 'series_hub_start' | 'series_hub_episode';
diff --git a/src/features/blog/ui/components/TableOfContents.test.tsx b/blog/ui/components/TableOfContents.test.tsx
similarity index 100%
rename from src/features/blog/ui/components/TableOfContents.test.tsx
rename to blog/ui/components/TableOfContents.test.tsx
diff --git a/src/features/blog/ui/components/TableOfContents/TableOfContents.tsx b/blog/ui/components/TableOfContents/TableOfContents.tsx
similarity index 100%
rename from src/features/blog/ui/components/TableOfContents/TableOfContents.tsx
rename to blog/ui/components/TableOfContents/TableOfContents.tsx
diff --git a/src/features/blog/ui/components/TableOfContents/index.ts b/blog/ui/components/TableOfContents/index.ts
similarity index 100%
rename from src/features/blog/ui/components/TableOfContents/index.ts
rename to blog/ui/components/TableOfContents/index.ts
diff --git a/src/features/blog/ui/components/ViewCounter.test.tsx b/blog/ui/components/ViewCounter.test.tsx
similarity index 97%
rename from src/features/blog/ui/components/ViewCounter.test.tsx
rename to blog/ui/components/ViewCounter.test.tsx
index 22d0665..3121e25 100644
--- a/src/features/blog/ui/components/ViewCounter.test.tsx
+++ b/blog/ui/components/ViewCounter.test.tsx
@@ -5,7 +5,7 @@ import ViewCounter from './ViewCounter';
const mockTrackView = vi.fn();
const mockGetViewCount = vi.fn();
-vi.mock('@/app/actions/view', () => ({
+vi.mock('@/blog/api/view', () => ({
getViewCount: (...args: unknown[]) => mockGetViewCount(...args),
trackView: (...args: unknown[]) => mockTrackView(...args),
}));
diff --git a/src/features/blog/ui/components/ViewCounter.tsx b/blog/ui/components/ViewCounter.tsx
similarity index 95%
rename from src/features/blog/ui/components/ViewCounter.tsx
rename to blog/ui/components/ViewCounter.tsx
index 4ec6a5e..bdee08d 100644
--- a/src/features/blog/ui/components/ViewCounter.tsx
+++ b/blog/ui/components/ViewCounter.tsx
@@ -1,7 +1,7 @@
'use client';
import { useEffect, useState } from 'react';
-import { getViewCount, trackView } from '@/app/actions/view';
+import { getViewCount, trackView } from '@/blog/api/view';
interface ViewCounterProps {
slug: string;
diff --git a/src/features/blog/ui/components/index.ts b/blog/ui/components/index.ts
similarity index 100%
rename from src/features/blog/ui/components/index.ts
rename to blog/ui/components/index.ts
diff --git a/src/features/blog/ui/mdx/components.tsx b/blog/ui/mdx/components.tsx
similarity index 90%
rename from src/features/blog/ui/mdx/components.tsx
rename to blog/ui/mdx/components.tsx
index 0132f08..b73d0a7 100644
--- a/src/features/blog/ui/mdx/components.tsx
+++ b/blog/ui/mdx/components.tsx
@@ -10,36 +10,36 @@ import {
ImageGrid,
MermaidDiagram,
ScrollWorkflow,
-} from '@/features/blog/ui/components';
+} from '@/blog/ui/components';
// Dynamic imports for visualization components (code splitting)
const BinarySearchVisualization = dynamic(() =>
- import('@/components/visualization').then((mod) => ({
+ import('@/shared/visualization').then((mod) => ({
default: mod.BinarySearchVisualization,
}))
);
const DPVisualization = dynamic(() =>
- import('@/components/visualization').then((mod) => ({
+ import('@/shared/visualization').then((mod) => ({
default: mod.DPVisualization,
}))
);
const GraphTraversalVisualization = dynamic(() =>
- import('@/components/visualization').then((mod) => ({
+ import('@/shared/visualization').then((mod) => ({
default: mod.GraphTraversalVisualization,
}))
);
const SlidingWindowVisualization = dynamic(() =>
- import('@/components/visualization').then((mod) => ({
+ import('@/shared/visualization').then((mod) => ({
default: mod.SlidingWindowVisualization,
}))
);
const SortingVisualization = dynamic(() =>
- import('@/components/visualization').then((mod) => ({
+ import('@/shared/visualization').then((mod) => ({
default: mod.SortingVisualization,
}))
);
const TwoPointerVisualization = dynamic(() =>
- import('@/components/visualization').then((mod) => ({
+ import('@/shared/visualization').then((mod) => ({
default: mod.TwoPointerVisualization,
}))
);
diff --git a/src/features/blog/ui/pages/BlogListClient.tsx b/blog/ui/pages/BlogListClient.tsx
similarity index 87%
rename from src/features/blog/ui/pages/BlogListClient.tsx
rename to blog/ui/pages/BlogListClient.tsx
index 73e4d0f..0102fc9 100644
--- a/src/features/blog/ui/pages/BlogListClient.tsx
+++ b/blog/ui/pages/BlogListClient.tsx
@@ -1,9 +1,9 @@
'use client';
import { useState, useMemo } from 'react';
-import { CategoryFilter, PostList } from '@/features/blog/ui/components';
-import type { Category } from '@/features/blog/ui/components';
-import type { FeedData } from '@/domains/post/model/types';
+import { CategoryFilter, PostList } from '@/blog/ui/components';
+import type { Category } from '@/blog/ui/components';
+import type { FeedData } from '@/blog/model/types';
interface BlogListClientProps {
posts: FeedData[];
diff --git a/src/features/blog/ui/pages/BlogListPage.tsx b/blog/ui/pages/BlogListPage.tsx
similarity index 91%
rename from src/features/blog/ui/pages/BlogListPage.tsx
rename to blog/ui/pages/BlogListPage.tsx
index 69d159a..874c5fa 100644
--- a/src/features/blog/ui/pages/BlogListPage.tsx
+++ b/blog/ui/pages/BlogListPage.tsx
@@ -1,5 +1,5 @@
import { Metadata } from 'next';
-import { getSortedFeedData } from '@/features/blog/services/post-repository';
+import { getSortedFeedData } from '@/blog/services/post-repository';
import { Container } from '@/shared/layout';
import BlogListClient from './BlogListClient';
diff --git a/src/features/blog/ui/pages/BlogPostPage.tsx b/blog/ui/pages/BlogPostPage.tsx
similarity index 90%
rename from src/features/blog/ui/pages/BlogPostPage.tsx
rename to blog/ui/pages/BlogPostPage.tsx
index d567891..ca5a933 100644
--- a/src/features/blog/ui/pages/BlogPostPage.tsx
+++ b/blog/ui/pages/BlogPostPage.tsx
@@ -4,11 +4,11 @@ import {
getFeedData,
getAllFeedSlugs,
getSeriesPosts,
-} from '@/features/blog/services/post-repository';
+} from '@/blog/services/post-repository';
import {
getMdxSource,
parseHeadingsFromMdx,
-} from '@/features/blog/services/markdown-parser';
+} from '@/blog/services/markdown-parser';
import { Container } from '@/shared/layout';
import {
ReadingProgress,
@@ -16,13 +16,13 @@ import {
GiscusComments,
SeriesNavigation,
ViewCounter,
-} from '@/features/blog/ui/components';
-import PostViewTracker from '@/shared/analytics/components/PostViewTracker';
-import DwellTimeTracker from '@/shared/analytics/components/DwellTimeTracker';
-import ScrollDepthTracker from '@/shared/analytics/components/ScrollDepthTracker';
-import JsonLd from '@/shared/seo/JsonLd';
-import { getMDXComponents } from '@/features/blog/ui/mdx/components';
-import { SITE_URL } from '@/core/config/site';
+} from '@/blog/ui/components';
+import PostViewTracker from '@/platform/analytics/components/PostViewTracker';
+import DwellTimeTracker from '@/platform/analytics/components/DwellTimeTracker';
+import ScrollDepthTracker from '@/platform/analytics/components/ScrollDepthTracker';
+import JsonLd from '@/platform/seo/JsonLd';
+import { getMDXComponents } from '@/blog/ui/mdx/components';
+import { SITE_URL } from '@/site/config/site';
export async function generateStaticParams() {
return getAllFeedSlugs();
diff --git a/src/features/blog/ui/pages/EngineeringPage.test.tsx b/blog/ui/pages/EngineeringPage.test.tsx
similarity index 92%
rename from src/features/blog/ui/pages/EngineeringPage.test.tsx
rename to blog/ui/pages/EngineeringPage.test.tsx
index 40d1fb2..768282e 100644
--- a/src/features/blog/ui/pages/EngineeringPage.test.tsx
+++ b/blog/ui/pages/EngineeringPage.test.tsx
@@ -1,7 +1,7 @@
import { render, screen } from '@testing-library/react';
import type { ReactNode } from 'react';
import { describe, expect, it, vi } from 'vitest';
-import type { FeedData } from '@/domains/post/model/types';
+import type { FeedData } from '@/blog/model/types';
import EngineeringPage from './EngineeringPage';
const mockGetSortedFeedData = vi.fn<() => FeedData[]>(() => []);
@@ -11,7 +11,7 @@ vi.mock('@/shared/layout', () => ({
Container: ({ children }: { children: ReactNode }) =>
{children}
,
}));
-vi.mock('@/features/blog/services/post-repository', () => ({
+vi.mock('@/blog/services/post-repository', () => ({
getSortedFeedData: () => mockGetSortedFeedData(),
}));
diff --git a/src/features/blog/ui/pages/EngineeringPage.tsx b/blog/ui/pages/EngineeringPage.tsx
similarity index 92%
rename from src/features/blog/ui/pages/EngineeringPage.tsx
rename to blog/ui/pages/EngineeringPage.tsx
index ceea957..bfcba87 100644
--- a/src/features/blog/ui/pages/EngineeringPage.tsx
+++ b/blog/ui/pages/EngineeringPage.tsx
@@ -1,6 +1,6 @@
import { Metadata } from 'next';
import { Suspense } from 'react';
-import { getSortedFeedData } from '@/features/blog/services/post-repository';
+import { getSortedFeedData } from '@/blog/services/post-repository';
import { Container } from '@/shared/layout';
import EngineeringPageClient from './EngineeringPageClient';
diff --git a/src/features/blog/ui/pages/EngineeringPageClient.test.tsx b/blog/ui/pages/EngineeringPageClient.test.tsx
similarity index 96%
rename from src/features/blog/ui/pages/EngineeringPageClient.test.tsx
rename to blog/ui/pages/EngineeringPageClient.test.tsx
index b03a7d8..923139b 100644
--- a/src/features/blog/ui/pages/EngineeringPageClient.test.tsx
+++ b/blog/ui/pages/EngineeringPageClient.test.tsx
@@ -1,6 +1,6 @@
import { fireEvent, render, screen } from '@testing-library/react';
import { beforeEach, describe, expect, it, vi } from 'vitest';
-import type { FeedData } from '@/domains/post/model/types';
+import type { FeedData } from '@/blog/model/types';
import EngineeringPageClient from './EngineeringPageClient';
let mockQueryString = '';
@@ -14,7 +14,7 @@ vi.mock('next/navigation', () => ({
useSearchParams: () => new URLSearchParams(mockQueryString),
}));
-vi.mock('@/features/blog/ui/components', () => ({
+vi.mock('@/blog/ui/components', () => ({
PostList: ({ posts }: { posts: FeedData[] }) => (
{posts.map((post) => post.slug).join(',')}
),
diff --git a/src/features/blog/ui/pages/EngineeringPageClient.tsx b/blog/ui/pages/EngineeringPageClient.tsx
similarity index 97%
rename from src/features/blog/ui/pages/EngineeringPageClient.tsx
rename to blog/ui/pages/EngineeringPageClient.tsx
index 8b976ee..59f2d2d 100644
--- a/src/features/blog/ui/pages/EngineeringPageClient.tsx
+++ b/blog/ui/pages/EngineeringPageClient.tsx
@@ -3,8 +3,8 @@
import { useEffect, useMemo } from 'react';
import { usePathname, useRouter, useSearchParams } from 'next/navigation';
import { clsx } from 'clsx';
-import type { FeedData } from '@/domains/post/model/types';
-import { PostList } from '@/features/blog/ui/components';
+import type { FeedData } from '@/blog/model/types';
+import { PostList } from '@/blog/ui/components';
const ARTICLE_PAGE_SIZE = 5;
interface EngineeringPageClientProps {
diff --git a/src/features/blog/ui/pages/EngineeringSeriesPage.test.tsx b/blog/ui/pages/EngineeringSeriesPage.test.tsx
similarity index 95%
rename from src/features/blog/ui/pages/EngineeringSeriesPage.test.tsx
rename to blog/ui/pages/EngineeringSeriesPage.test.tsx
index 6d6340a..a970ed9 100644
--- a/src/features/blog/ui/pages/EngineeringSeriesPage.test.tsx
+++ b/blog/ui/pages/EngineeringSeriesPage.test.tsx
@@ -1,7 +1,7 @@
import { render, screen } from '@testing-library/react';
import type { ReactNode } from 'react';
import { beforeEach, describe, expect, it, vi } from 'vitest';
-import type { FeedData } from '@/domains/post/model/types';
+import type { FeedData } from '@/blog/model/types';
import EngineeringSeriesPage from './EngineeringSeriesPage';
const mockGetSortedFeedData = vi.fn<() => FeedData[]>(() => []);
@@ -14,7 +14,7 @@ vi.mock('@/shared/layout', () => ({
Container: ({ children }: { children: ReactNode }) => {children}
,
}));
-vi.mock('@/features/blog/services/post-repository', () => ({
+vi.mock('@/blog/services/post-repository', () => ({
getSortedFeedData: () => mockGetSortedFeedData(),
}));
diff --git a/src/features/blog/ui/pages/EngineeringSeriesPage.tsx b/blog/ui/pages/EngineeringSeriesPage.tsx
similarity index 95%
rename from src/features/blog/ui/pages/EngineeringSeriesPage.tsx
rename to blog/ui/pages/EngineeringSeriesPage.tsx
index a0b3281..3f8ab9e 100644
--- a/src/features/blog/ui/pages/EngineeringSeriesPage.tsx
+++ b/blog/ui/pages/EngineeringSeriesPage.tsx
@@ -1,7 +1,7 @@
import Link from 'next/link';
import { notFound } from 'next/navigation';
-import { getSeriesSummaries } from '@/features/blog/model/series-group';
-import { getSortedFeedData } from '@/features/blog/services/post-repository';
+import { getSeriesSummaries } from '@/blog/model/series-group';
+import { getSortedFeedData } from '@/blog/services/post-repository';
import { Container } from '@/shared/layout';
interface EngineeringSeriesPageProps {
diff --git a/src/features/blog/ui/pages/LifePage.test.tsx b/blog/ui/pages/LifePage.test.tsx
similarity index 87%
rename from src/features/blog/ui/pages/LifePage.test.tsx
rename to blog/ui/pages/LifePage.test.tsx
index 0cccbc7..bb8ec5d 100644
--- a/src/features/blog/ui/pages/LifePage.test.tsx
+++ b/blog/ui/pages/LifePage.test.tsx
@@ -1,7 +1,7 @@
import { render, screen } from '@testing-library/react';
import type { ReactNode } from 'react';
import { describe, expect, it, vi } from 'vitest';
-import type { FeedData } from '@/domains/post/model/types';
+import type { FeedData } from '@/blog/model/types';
import LifePage from './LifePage';
const mockGetSortedFeedData = vi.fn<() => FeedData[]>(() => []);
@@ -11,11 +11,11 @@ vi.mock('@/shared/layout', () => ({
Container: ({ children }: { children: ReactNode }) => {children}
,
}));
-vi.mock('@/features/blog/services/post-repository', () => ({
+vi.mock('@/blog/services/post-repository', () => ({
getSortedFeedData: () => mockGetSortedFeedData(),
}));
-vi.mock('@/features/blog/ui/components', () => ({
+vi.mock('@/blog/ui/components', () => ({
PostList: ({ posts }: { posts: FeedData[] }) => (
posts:{posts.length}
),
diff --git a/src/features/blog/ui/pages/LifePage.tsx b/blog/ui/pages/LifePage.tsx
similarity index 86%
rename from src/features/blog/ui/pages/LifePage.tsx
rename to blog/ui/pages/LifePage.tsx
index ca8b178..f032b0e 100644
--- a/src/features/blog/ui/pages/LifePage.tsx
+++ b/blog/ui/pages/LifePage.tsx
@@ -1,7 +1,7 @@
import { Metadata } from 'next';
-import { getSortedFeedData } from '@/features/blog/services/post-repository';
+import { getSortedFeedData } from '@/blog/services/post-repository';
import { Container } from '@/shared/layout';
-import { PostList } from '@/features/blog/ui/components';
+import { PostList } from '@/blog/ui/components';
export const metadata: Metadata = {
title: 'Life',
diff --git a/src/features/blog/ui/pages/SeriesPage.test.tsx b/blog/ui/pages/SeriesPage.test.tsx
similarity index 90%
rename from src/features/blog/ui/pages/SeriesPage.test.tsx
rename to blog/ui/pages/SeriesPage.test.tsx
index 8386478..c3418d1 100644
--- a/src/features/blog/ui/pages/SeriesPage.test.tsx
+++ b/blog/ui/pages/SeriesPage.test.tsx
@@ -1,8 +1,8 @@
import { render, screen } from '@testing-library/react';
import type { ReactNode } from 'react';
import { describe, expect, it, vi } from 'vitest';
-import type { FeedData } from '@/domains/post/model/types';
-import type { SeriesSummary } from '@/features/blog/model/series-group';
+import type { FeedData } from '@/blog/model/types';
+import type { SeriesSummary } from '@/blog/model/series-group';
import SeriesPage from './SeriesPage';
const mockGetSortedFeedData = vi.fn<() => FeedData[]>(() => []);
@@ -28,14 +28,14 @@ vi.mock('@/shared/layout', () => ({
Container: ({ children }: { children: ReactNode }) => {children}
,
}));
-vi.mock('@/features/blog/services/post-repository', () => ({
+vi.mock('@/blog/services/post-repository', () => ({
getSortedFeedData: () => mockGetSortedFeedData(),
}));
-vi.mock('@/features/blog/model/series-group', async () => {
+vi.mock('@/blog/model/series-group', async () => {
const actual = await vi.importActual<
- typeof import('@/features/blog/model/series-group')
- >('@/features/blog/model/series-group');
+ typeof import('@/blog/model/series-group')
+ >('@/blog/model/series-group');
return {
...actual,
diff --git a/src/features/blog/ui/pages/SeriesPage.tsx b/blog/ui/pages/SeriesPage.tsx
similarity index 88%
rename from src/features/blog/ui/pages/SeriesPage.tsx
rename to blog/ui/pages/SeriesPage.tsx
index 74e2b63..53e04f6 100644
--- a/src/features/blog/ui/pages/SeriesPage.tsx
+++ b/blog/ui/pages/SeriesPage.tsx
@@ -1,9 +1,9 @@
import { Metadata } from 'next';
-import { getSortedFeedData } from '@/features/blog/services/post-repository';
+import { getSortedFeedData } from '@/blog/services/post-repository';
import { Container } from '@/shared/layout';
import { EmptyState } from '@/shared/ui';
-import { getSeriesSummaries } from '@/features/blog/model/series-group';
-import { SeriesHubList } from '@/features/blog/ui/components';
+import { getSeriesSummaries } from '@/blog/model/series-group';
+import { SeriesHubList } from '@/blog/ui/components';
export const metadata: Metadata = {
title: 'Series',
diff --git a/docs/DESIGN.md b/docs/DESIGN.md
index 25a50d1..2528f9b 100644
--- a/docs/DESIGN.md
+++ b/docs/DESIGN.md
@@ -1,12 +1,12 @@
# DESIGN
-Last updated: 2026-02-26
+Last updated: 2026-05-07
## Source of Truth
-1. Tokens: `src/styles/tokens.css`
-2. Global style behavior: `src/styles/globals.css`
-3. Reusable component patterns: `src/shared/ui/**`, `src/shared/layout/**`
+1. Tokens: `styles/tokens.css`
+2. Global style behavior: `styles/globals.css`
+3. Reusable component patterns: `shared/ui/**`, `shared/layout/**`
## Design Guardrails
diff --git a/docs/FRONTEND.md b/docs/FRONTEND.md
index 71d2664..6186d69 100644
--- a/docs/FRONTEND.md
+++ b/docs/FRONTEND.md
@@ -1,6 +1,6 @@
# FRONTEND
-Last updated: 2026-02-27
+Last updated: 2026-05-07
## Stack
@@ -11,17 +11,19 @@ Last updated: 2026-02-27
## Frontend Architecture
-1. Route layer: `src/app/**`
-2. Feature layer: `src/features/**`
-3. Shared layer: `src/shared/**` (+ visualization: `src/components/visualization/**`)
-4. Styles/tokens: `src/styles/**`
+1. Route adapter layer: `src/app/**`
+2. Domain modules: `blog/**`, `resume/**`, `search/**`
+3. Composition layer: `site/**`
+4. Runtime/integration layer: `platform/**`
+5. Domain-agnostic primitives: `shared/**`
+6. Styles/tokens: `styles/**`
## Frontend Rules
-1. Keep boundaries clear between feature modules (`blog`, `resume`, `search`, `home`) and shared modules (`layout`, `ui`, `analytics`, `providers`, `seo`).
+1. Keep boundaries clear between domain modules (`blog`, `resume`, `search`) and composition/infrastructure modules (`site`, `platform`, `shared`).
2. Avoid duplicated global trackers/providers in root layout.
-3. Use typed contracts from `src/domains/**/model/types.ts` and `src/shared/types/*`.
-4. Keep MDX custom component mappings centralized in `src/features/blog/ui/mdx/components.tsx`.
+3. Keep domain contracts inside the owning domain module, for example `blog/model/types.ts`.
+4. Keep MDX custom component mappings centralized in `blog/ui/mdx/components.tsx`.
## Test Expectations
diff --git a/docs/README.md b/docs/README.md
index bef4265..2929d31 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -1,10 +1,12 @@
# Docs Index
-Last updated: 2026-04-17
+Last updated: 2026-05-07
-This index tracks the active documentation that should stay aligned with the
-current codebase. If a document is missing here, add it when the document
-becomes part of the maintained workflow.
+This index tracks the smallest set of documentation that must stay aligned with
+the current codebase. It is not a full file inventory.
+
+Prefer updating an existing ADR, guide, exec plan, or README before adding a new
+standalone document.
## Repository-Level Docs
@@ -13,33 +15,19 @@ becomes part of the maintained workflow.
## Active Documentation
-- Engineering baseline:
- - `docs/FRONTEND.md`
- - `docs/DESIGN.md`
- - `docs/RELIABILITY.md`
- - `docs/SECURITY.md`
- - `docs/QUALITY_SCORE.md`
- - `docs/blog-quality-guide.md`
-- Delivery/process:
+- Decisions:
- `docs/adr/README.md`
+ - `docs/adr/*.md`
+- Planning:
- `docs/PLANS.md`
- `docs/exec-plans/active/README.md`
- - `docs/exec-plans/completed/README.md`
- `docs/exec-plans/tech-debt-tracker.md`
- - `docs/guides/agentation-workflow.md`
- - `docs/guides/pr-workflow.md`
- - `docs/guides/testing-guide.md`
- - `docs/guides/ui-components-guide.md`
-- Product/domain:
- - `docs/PRODUCT_SENSE.md`
- - `docs/product-specs/index.md`
- - `docs/product-specs/new-user-onboarding.md`
- - `docs/design-docs/index.md`
- - `docs/design-docs/core-beliefs.md`
-- Data/analytics:
- - `docs/analytics/analytics-kpi-weekly-template.md`
- - `docs/database/db-schema.md`
- - `docs/database/supabase-view-count.sql`
+- Maintained guides:
+ - `docs/guides/**`
+- Domain references:
+ - `docs/database/**`
+ - `docs/product-specs/**`
+ - `docs/design-docs/**`
## Reference / Research
@@ -56,6 +44,7 @@ becomes part of the maintained workflow.
1. Use repository-relative paths only in docs (no absolute local paths).
2. Update `Last updated` when editing policy/process docs.
-3. Move stale auto-generated reports to `docs/archive/` instead of deleting context.
-4. Keep commands copy-pastable from repository root.
-5. Remove or replace stale links when a referenced file no longer exists.
+3. Treat this file as a maintained-doc boundary, not a complete docs inventory.
+4. Move stale auto-generated reports to `docs/archive/` instead of deleting context.
+5. Keep commands copy-pastable from repository root.
+6. Remove or replace stale links when a referenced file no longer exists.
diff --git a/docs/adr/0004-preserve-feature-first-source-structure.md b/docs/adr/0004-preserve-feature-first-source-structure.md
index 8ae94ea..38945b0 100644
--- a/docs/adr/0004-preserve-feature-first-source-structure.md
+++ b/docs/adr/0004-preserve-feature-first-source-structure.md
@@ -1,7 +1,7 @@
# 0004. Feature-first 소스 구조를 유지한다
Date: 2026-02-28
-Status: Accepted
+Status: Superseded by [0011](0011-adopt-frontend-modular-monolith.md)
## 배경
diff --git a/docs/adr/0005-use-token-first-tailwind-styling.md b/docs/adr/0005-use-token-first-tailwind-styling.md
index 131c80b..1c020f1 100644
--- a/docs/adr/0005-use-token-first-tailwind-styling.md
+++ b/docs/adr/0005-use-token-first-tailwind-styling.md
@@ -1,7 +1,10 @@
# 0005. Token-first Tailwind 스타일링을 사용한다
Date: 2026-01-27
-Status: Accepted
+Status: Accepted, path amended by [0011](0011-adopt-frontend-modular-monolith.md)
+
+Note: ADR 0011 moves the physical style path from `src/styles/**` to `styles/**`
+while preserving the token-first styling decision.
## 배경
diff --git a/docs/adr/0006-isolate-visualization-heavy-components.md b/docs/adr/0006-isolate-visualization-heavy-components.md
index 610aa80..15dc8ee 100644
--- a/docs/adr/0006-isolate-visualization-heavy-components.md
+++ b/docs/adr/0006-isolate-visualization-heavy-components.md
@@ -1,7 +1,11 @@
# 0006. 시각화 중심 컴포넌트를 격리한다
Date: 2026-01-28
-Status: Accepted
+Status: Accepted, path amended by [0011](0011-adopt-frontend-modular-monolith.md)
+
+Note: ADR 0011 moves the physical visualization path from
+`src/components/visualization/**` to `shared/visualization/**` while preserving
+the isolation decision.
## 배경
diff --git a/docs/adr/0009-use-app-shell-for-primary-navigation.md b/docs/adr/0009-use-app-shell-for-primary-navigation.md
index 0982dd7..2f028f5 100644
--- a/docs/adr/0009-use-app-shell-for-primary-navigation.md
+++ b/docs/adr/0009-use-app-shell-for-primary-navigation.md
@@ -1,7 +1,11 @@
# 0009. 주요 탐색에 AppShell을 사용한다
Date: 2026-04-21
-Status: Accepted
+Status: Accepted, path amended by [0011](0011-adopt-frontend-modular-monolith.md)
+
+Note: ADR 0011 moves AppShell from `src/shared/layout/AppShell/**` to
+`site/shell/AppShell/**` while preserving the single app-shell navigation
+decision.
## 배경
diff --git a/docs/adr/0010-use-targeted-documentation-harness.md b/docs/adr/0010-use-targeted-documentation-harness.md
index 550e69f..4b11663 100644
--- a/docs/adr/0010-use-targeted-documentation-harness.md
+++ b/docs/adr/0010-use-targeted-documentation-harness.md
@@ -17,7 +17,7 @@ markdownlint 이슈 때문에, 현재 상태에서는 문서 변경 검증용 ha
## 결정
핵심 문서만 대상으로 하는 `npm run verify:docs`를 추가한다. 이 command는
-`internal/scripts/verify-docs.mjs`를 실행하며 다음을 검증한다.
+`tooling/scripts/verify-docs.mjs`를 실행하며 다음을 검증한다.
- `docs/README.md`에 등록된 명시적 문서 경로가 실제로 존재하는지 확인한다.
- `AGENTS.md`와 `docs/adr/README.md`가 같은 ADR 작성 조건을 포함하는지
diff --git a/docs/adr/0011-adopt-frontend-modular-monolith.md b/docs/adr/0011-adopt-frontend-modular-monolith.md
new file mode 100644
index 0000000..aa49a4d
--- /dev/null
+++ b/docs/adr/0011-adopt-frontend-modular-monolith.md
@@ -0,0 +1,71 @@
+# 0011. Domain-first Modular Monolith를 사용한다
+
+Date: 2026-05-07
+Status: Accepted
+
+## 배경
+
+기존 구조는 `src/features`, `src/domains`, `src/core`, `src/shared` 안에서
+책임을 나눴다. 하지만 실제 변경을 검토해 보니 주요 도메인이 독립된 모듈로
+분리됐다기보다 `src` 아래 하위 디렉터리를 다시 나눈 수준에 가까웠다.
+
+이 프로젝트에서 원하는 경계는 Next.js 소스 폴더 내부의 분류가 아니라,
+`posts/`처럼 저장소 최상단에서 주요 도메인이 눈에 보이는 구조다. 글, 이력서,
+검색은 서로 다른 변경 이유와 언어를 가진다. 반면 홈, AppShell, provider,
+analytics, Supabase, 공통 UI는 도메인이 아니라 조합 또는 인프라 책임이다.
+
+## 결정
+
+DDD의 전략적 설계 개념을 차용한 domain-first modular monolith를 사용한다.
+다만 Entity, Aggregate, Repository 같은 tactical DDD pattern을 모든 모듈에
+강제하지 않는다.
+
+최상위 모듈 경계는 다음과 같이 둔다.
+
+- `src/app/`: Next.js App Router route adapter, route handler, metadata entry.
+- `posts/`: 블로그 원문 콘텐츠 저장소. `index.mdx`와 `meta.json` 구조를 유지한다.
+- `blog/`: 글 도메인. post schema, repository, publication policy, series,
+ blog UI, view-count use case를 소유한다.
+- `resume/`: 이력서 도메인. resume data, ordering, resume UI를 소유한다.
+- `search/`: 검색 도메인. command palette, search action, recommendation을
+ 소유한다.
+- `site/`: 도메인 조합 layer. home, AppShell, navigation, provider, site config를
+ 소유한다.
+- `platform/`: 외부/런타임 인프라. Supabase, Umami analytics, SEO helper,
+ devtool integration을 소유한다.
+- `shared/`: 도메인 지식이 없는 UI primitive, motion helper, testing helper,
+ visualization widget만 둔다.
+- `styles/`: design token과 global style을 둔다.
+
+의존 방향은 `src/app -> site -> domain -> platform/shared`를 기본으로 한다.
+`blog`는 `posts/`를 읽을 수 있는 유일한 도메인 모듈이다. `site`는 여러 도메인을
+조합할 수 있지만, 도메인 내부 정책을 대신 구현하지 않는다.
+
+## 결과
+
+- 저장소 최상단에서 주요 도메인과 조합/인프라 책임이 구분된다.
+- `src/app`은 Next.js route adapter 역할에 집중한다.
+- 기존 `src/features`, `src/domains`, `src/core`, `src/shared` 경계는 이 결정으로
+ 대체된다.
+- 단순 폴더 이동만으로 경계가 보장되지는 않는다. import 경계는 public API와
+ 테스트로 우선 관리하고, lint 기반 강제는 별도 결정으로 추가할 수 있다.
+- `shared`로 올리는 코드는 도메인 언어를 포함하지 않아야 한다.
+
+## 검토한 대안
+
+- 기존 feature-first 구조 유지: 변경량은 적지만 사용자가 기대한 최상위 도메인
+ 경계를 만들지 못한다.
+- 모든 코드를 `src/shared` 또는 `src/features` 안에 재분류: import는 단순하지만
+ 도메인 소유권이 흐려진다.
+- DDD tactical pattern 전면 적용: 작은 블로그 프로젝트에는 추상화 비용이 크고,
+ 실제 문제인 모듈 경계보다 형식이 앞설 수 있다.
+- `home`을 독립 도메인으로 승격: 홈은 자체 도메인보다 `blog`, `resume`, `search`를
+ 엮는 조합 화면이므로 `site/home`에 둔다.
+
+## 관련 히스토리
+
+- `docs/adr/0004-preserve-feature-first-source-structure.md`: 이전 feature-first
+ 소스 구조 결정. 이 ADR이 대체한다.
+- `docs/design-docs/adr-001-modular-monolith.md`: 최상위 도메인 경계가 아닌
+ `src/features` 재분류를 modular monolith로 설명하던 초안. 이 ADR로 대체하고
+ 제거한다.
diff --git a/docs/adr/README.md b/docs/adr/README.md
index b842157..7ed8058 100644
--- a/docs/adr/README.md
+++ b/docs/adr/README.md
@@ -21,13 +21,14 @@ ADR은 AI 협업 가이드와 별개의 문서다. 사람이 결정했든 AI가
| [0001](0001-use-nextjs-app-router.md) | Accepted | 블로그 런타임으로 Next.js App Router를 사용한다 |
| [0002](0002-keep-custom-mdx-webpack-pipeline.md) | Accepted | 커스텀 MDX webpack 파이프라인을 유지한다 |
| [0003](0003-store-posts-as-folder-mdx-and-meta.md) | Accepted | 글을 중첩 가능한 `index.mdx`와 `meta.json` 폴더로 저장한다 |
-| [0004](0004-preserve-feature-first-source-structure.md) | Accepted | feature-first 소스 구조를 유지한다 |
+| [0004](0004-preserve-feature-first-source-structure.md) | Superseded | feature-first 소스 구조를 유지한다 |
| [0005](0005-use-token-first-tailwind-styling.md) | Accepted | token-first Tailwind 스타일링을 사용한다 |
| [0006](0006-isolate-visualization-heavy-components.md) | Accepted | 시각화 중심 컴포넌트를 격리한다 |
| [0007](0007-use-umami-and-supabase-view-counts.md) | Accepted | Umami 분석과 Supabase 조회수를 함께 사용한다 |
| [0008](0008-enforce-publication-policy-in-content-ingestion.md) | Accepted | 콘텐츠 수집 경로에서 공개 정책을 강제한다 |
| [0009](0009-use-app-shell-for-primary-navigation.md) | Accepted | 주요 탐색과 레이아웃에 AppShell을 사용한다 |
| [0010](0010-use-targeted-documentation-harness.md) | Accepted | 핵심 문서 검증에 targeted documentation harness를 사용한다 |
+| [0011](0011-adopt-frontend-modular-monolith.md) | Accepted | Domain-first Modular Monolith를 사용한다 |
## 작성 조건
diff --git a/docs/database/db-schema.md b/docs/database/db-schema.md
index ca597ff..afece86 100644
--- a/docs/database/db-schema.md
+++ b/docs/database/db-schema.md
@@ -3,9 +3,9 @@
Generated from:
- `docs/database/supabase-view-count.sql`
-- `src/shared/integrations/supabase.ts`
+- `platform/integrations/supabase.ts`
-Last updated: 2026-04-16
+Last updated: 2026-05-07
## Table: `public.views`
@@ -47,7 +47,7 @@ Last updated: 2026-04-16
## Type Mapping (App)
-`src/shared/integrations/supabase.ts` defines:
+`platform/integrations/supabase.ts` defines:
- Table row type for `views`.
- RPC signature:
diff --git a/docs/guides/testing-guide.md b/docs/guides/testing-guide.md
index 949ea5e..eada71c 100644
--- a/docs/guides/testing-guide.md
+++ b/docs/guides/testing-guide.md
@@ -17,44 +17,39 @@
### lib
-- `src/shared/analytics/lib/analytics.test.ts`
+- `platform/analytics/lib/analytics.test.ts`
- GA ID 부재/부재 조건에서 이벤트 미발송
- trackEvent, trackPageView 실 발송 가드
-- `src/features/blog/services/post-repository.test.ts`
+- `blog/services/post-repository.test.ts`
- 정렬/시리즈 조회/SKU 슬러그 수집
-- `src/features/search/model/get-search-actions.test.ts`
+- `search/model/get-search-actions.test.ts`
- 키워드 생성/nullable tags 처리
-- `src/features/blog/services/markdown-parser.test.ts`
+- `blog/services/markdown-parser.test.ts`
- TOC ID 생성 및 중복 처리
-### app actions
+### blog api
-- `src/app/actions/view.test.ts`
+- `blog/api/view.test.ts`
- 슬러그 정규화
- Supabase 미연결 환경 fallback
- RPC 실패 시 읽기 fallback 동작
### components
-- `src/shared/layout/Header/useScrollVisibility.test.ts`
- - 홈/블로그 경로별 top/bottom visibility 동작, 스크롤 임계값
- - 바텀바 오프셋 CSS 변수 동기화
-- `src/shared/layout/Header/MobileBottomNav.test.tsx`
+- `site/navigation/MobileBottomNav.test.tsx`
- 아이템 수, active 상태, 토큰 기반 class, search action, analytics tracking
-- `src/shared/layout/Header/Header.test.tsx`
- - 헤더 하위 컴포넌트 연결 및 top header 상태 반영
-- `src/features/blog/ui/components/*.test.tsx`
+- `blog/ui/components/*.test.tsx`
- PostCard/PostList/CategoryFilter 상태별 렌더/이벤트
-- `src/shared/ui/*.test.tsx`
+- `shared/ui/*.test.tsx`
- Button/EmptyState/Route state 상태 검증
### styles
-- `src/styles/tokens.test.ts`
-- `src/styles/globals.test.ts`
+- `styles/tokens.test.ts`
+- `styles/globals.test.ts`
## 문서 하네스
diff --git a/docs/references/uv-llms.txt b/docs/references/uv-llms.txt
index d8d5fba..fe9814f 100644
--- a/docs/references/uv-llms.txt
+++ b/docs/references/uv-llms.txt
@@ -3,8 +3,8 @@
1) Current project toolchain is Node/TypeScript-first.
2) Python is used only for specific analysis scripts:
-- internal/scripts/research/build_nekaracuba_corpus.py
-- internal/scripts/research/generate_toss_analysis.py
+- tooling/scripts/research/build_nekaracuba_corpus.py
+- tooling/scripts/research/generate_toss_analysis.py
If standardizing Python tooling with `uv`:
diff --git a/package.json b/package.json
index 7c97e7e..1a35e8c 100644
--- a/package.json
+++ b/package.json
@@ -4,33 +4,33 @@
"private": true,
"type": "module",
"scripts": {
- "dev": "node internal/scripts/dev-with-agentation.mjs",
+ "dev": "node tooling/scripts/dev-with-agentation.mjs",
"dev:next": "next dev --webpack",
"dev:agentation": "agentation-mcp server",
"build": "next build --webpack",
"start": "next start",
"lint": "eslint . --ext .js,.jsx,.ts,.tsx",
- "lint:css:syntax": "node internal/scripts/check-css-syntax.mjs",
- "verify:docs": "node internal/scripts/verify-docs.mjs",
+ "lint:css:syntax": "node tooling/scripts/check-css-syntax.mjs",
+ "verify:docs": "node tooling/scripts/verify-docs.mjs",
"analyze": "ANALYZE=true next build --webpack",
"test": "vitest",
"test:unit": "vitest run",
- "test:components": "vitest run src/features/**/*.test.ts src/features/**/*.test.tsx src/shared/**/*.test.ts src/shared/**/*.test.tsx",
+ "test:components": "vitest run blog resume search shared site platform styles",
"test:coverage": "vitest run --coverage",
"test:ci": "npm run lint && npm run lint:css:syntax && npm run verify:docs && npm run test:unit && npm run test:e2e",
"test:e2e": "npx playwright test",
- "test:smoke": "vitest run src/shared/**/*.test.ts src/app/actions/*.test.ts && npx playwright test --grep @smoke",
- "new-post": "node internal/scripts/posts/new-post.js",
- "content:audit": "node internal/scripts/posts/audit-post-quality.mjs",
- "localize:images": "node internal/scripts/posts/localize-post-images.mjs",
+ "test:smoke": "vitest run shared/**/*.test.ts shared/**/*.test.tsx blog/api/*.test.ts && npx playwright test --grep @smoke",
+ "new-post": "node tooling/scripts/posts/new-post.js",
+ "content:audit": "node tooling/scripts/posts/audit-post-quality.mjs",
+ "localize:images": "node tooling/scripts/posts/localize-post-images.mjs",
"format": "prettier --write .",
- "lint:md": "markdownlint-cli2 --config internal/config/.markdownlint-cli2.jsonc '**/*.md' '**/*.mdx'",
- "lint:spell": "cspell --config internal/config/cspell.json '**/*'",
+ "lint:md": "markdownlint-cli2 --config tooling/config/.markdownlint-cli2.jsonc '**/*.md' '**/*.mdx'",
+ "lint:spell": "cspell --config tooling/config/cspell.json '**/*'",
"prepare": "husky"
},
"lint-staged": {
"*.{js,jsx,ts,tsx,json,css,md,mdx}": "prettier --write",
- "*.{md,mdx}": "markdownlint-cli2 --config internal/config/.markdownlint-cli2.jsonc"
+ "*.{md,mdx}": "markdownlint-cli2 --config tooling/config/.markdownlint-cli2.jsonc"
},
"prettier": {
"semi": true,
diff --git a/src/shared/analytics/components/DwellTimeTracker.test.tsx b/platform/analytics/components/DwellTimeTracker.test.tsx
similarity index 92%
rename from src/shared/analytics/components/DwellTimeTracker.test.tsx
rename to platform/analytics/components/DwellTimeTracker.test.tsx
index e6df49e..8060020 100644
--- a/src/shared/analytics/components/DwellTimeTracker.test.tsx
+++ b/platform/analytics/components/DwellTimeTracker.test.tsx
@@ -1,9 +1,9 @@
import { act, render } from '@testing-library/react';
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import DwellTimeTracker from './DwellTimeTracker';
-import { trackEvent } from '@/shared/analytics/lib/analytics';
+import { trackEvent } from '@/platform/analytics/lib/analytics';
-vi.mock('@/shared/analytics/lib/analytics', () => ({
+vi.mock('@/platform/analytics/lib/analytics', () => ({
trackEvent: vi.fn(),
}));
diff --git a/src/shared/analytics/components/DwellTimeTracker.tsx b/platform/analytics/components/DwellTimeTracker.tsx
similarity index 97%
rename from src/shared/analytics/components/DwellTimeTracker.tsx
rename to platform/analytics/components/DwellTimeTracker.tsx
index 9e9ec61..f029643 100644
--- a/src/shared/analytics/components/DwellTimeTracker.tsx
+++ b/platform/analytics/components/DwellTimeTracker.tsx
@@ -1,7 +1,7 @@
'use client';
import { useEffect, useRef } from 'react';
-import { trackEvent } from '@/shared/analytics/lib/analytics';
+import { trackEvent } from '@/platform/analytics/lib/analytics';
interface DwellTimeTrackerProps {
slug: string;
diff --git a/src/shared/analytics/components/PostViewTracker.tsx b/platform/analytics/components/PostViewTracker.tsx
similarity index 87%
rename from src/shared/analytics/components/PostViewTracker.tsx
rename to platform/analytics/components/PostViewTracker.tsx
index a69dd4e..8da54f6 100644
--- a/src/shared/analytics/components/PostViewTracker.tsx
+++ b/platform/analytics/components/PostViewTracker.tsx
@@ -1,7 +1,7 @@
'use client';
import { useEffect } from 'react';
-import { trackEvent } from '@/shared/analytics/lib/analytics';
+import { trackEvent } from '@/platform/analytics/lib/analytics';
interface PostViewTrackerProps {
slug: string;
diff --git a/src/shared/analytics/components/ScrollDepthTracker.tsx b/platform/analytics/components/ScrollDepthTracker.tsx
similarity index 96%
rename from src/shared/analytics/components/ScrollDepthTracker.tsx
rename to platform/analytics/components/ScrollDepthTracker.tsx
index d6431fa..5bfa8f9 100644
--- a/src/shared/analytics/components/ScrollDepthTracker.tsx
+++ b/platform/analytics/components/ScrollDepthTracker.tsx
@@ -1,7 +1,7 @@
'use client';
import { useEffect, useRef, useCallback } from 'react';
-import { trackEvent } from '@/shared/analytics/lib/analytics';
+import { trackEvent } from '@/platform/analytics/lib/analytics';
interface ScrollDepthTrackerProps {
slug: string;
diff --git a/src/shared/analytics/components/UmamiAnalytics.tsx b/platform/analytics/components/UmamiAnalytics.tsx
similarity index 85%
rename from src/shared/analytics/components/UmamiAnalytics.tsx
rename to platform/analytics/components/UmamiAnalytics.tsx
index 52c0b39..cec40dd 100644
--- a/src/shared/analytics/components/UmamiAnalytics.tsx
+++ b/platform/analytics/components/UmamiAnalytics.tsx
@@ -1,7 +1,7 @@
'use client';
import Script from 'next/script';
-import { flushQueuedUmamiEvents } from '@/shared/analytics/lib/analytics';
+import { flushQueuedUmamiEvents } from '@/platform/analytics/lib/analytics';
const UMAMI_URL = process.env.NEXT_PUBLIC_UMAMI_URL;
const UMAMI_WEBSITE_ID = process.env.NEXT_PUBLIC_UMAMI_WEBSITE_ID;
diff --git a/src/shared/analytics/lib/analytics.test.ts b/platform/analytics/lib/analytics.test.ts
similarity index 100%
rename from src/shared/analytics/lib/analytics.test.ts
rename to platform/analytics/lib/analytics.test.ts
diff --git a/src/shared/analytics/lib/analytics.ts b/platform/analytics/lib/analytics.ts
similarity index 100%
rename from src/shared/analytics/lib/analytics.ts
rename to platform/analytics/lib/analytics.ts
diff --git a/src/shared/devtools/AgentationOverlay.test.tsx b/platform/devtools/AgentationOverlay.test.tsx
similarity index 100%
rename from src/shared/devtools/AgentationOverlay.test.tsx
rename to platform/devtools/AgentationOverlay.test.tsx
diff --git a/src/shared/devtools/AgentationOverlay.tsx b/platform/devtools/AgentationOverlay.tsx
similarity index 100%
rename from src/shared/devtools/AgentationOverlay.tsx
rename to platform/devtools/AgentationOverlay.tsx
diff --git a/platform/index.ts b/platform/index.ts
new file mode 100644
index 0000000..529a902
--- /dev/null
+++ b/platform/index.ts
@@ -0,0 +1,10 @@
+export {
+ AnalyticsEvents,
+ flushQueuedUmamiEvents,
+ trackEvent,
+ type AnalyticsEventName,
+} from './analytics/lib/analytics';
+export {
+ getSupabaseServerClient,
+ type SupabaseDatabase,
+} from './integrations/supabase';
diff --git a/src/shared/integrations/supabase.test.ts b/platform/integrations/supabase.test.ts
similarity index 100%
rename from src/shared/integrations/supabase.test.ts
rename to platform/integrations/supabase.test.ts
diff --git a/src/shared/integrations/supabase.ts b/platform/integrations/supabase.ts
similarity index 100%
rename from src/shared/integrations/supabase.ts
rename to platform/integrations/supabase.ts
diff --git a/src/shared/seo/JsonLd.tsx b/platform/seo/JsonLd.tsx
similarity index 100%
rename from src/shared/seo/JsonLd.tsx
rename to platform/seo/JsonLd.tsx
diff --git a/resume/index.ts b/resume/index.ts
new file mode 100644
index 0000000..c8d6ce2
--- /dev/null
+++ b/resume/index.ts
@@ -0,0 +1,22 @@
+export {
+ activities,
+ certifications,
+ education,
+ experiences,
+ personalInfo,
+ personalProjects,
+} from './model/resume-data';
+export { orderExperienceStages } from './model/order-experience-stages';
+export type {
+ Activity,
+ Certification,
+ Education,
+ Experience,
+ ExperienceProject,
+ ExperienceStage,
+ ExperienceStageKey,
+ PersonalInfo,
+ PersonalProject,
+ ProjectLink,
+ SkillGroup,
+} from './model/types';
diff --git a/src/features/resume/model/order-experience-stages.test.ts b/resume/model/order-experience-stages.test.ts
similarity index 96%
rename from src/features/resume/model/order-experience-stages.test.ts
rename to resume/model/order-experience-stages.test.ts
index b94b52f..dde5e58 100644
--- a/src/features/resume/model/order-experience-stages.test.ts
+++ b/resume/model/order-experience-stages.test.ts
@@ -1,6 +1,6 @@
import { describe, expect, it } from 'vitest';
import { orderExperienceStages } from './order-experience-stages';
-import type { ExperienceStage } from '@/domains/resume/model/types';
+import type { ExperienceStage } from '@/resume/model/types';
describe('orderExperienceStages', () => {
it('orders core stages by fixed priority regardless of input order', () => {
diff --git a/src/features/resume/model/order-experience-stages.ts b/resume/model/order-experience-stages.ts
similarity index 89%
rename from src/features/resume/model/order-experience-stages.ts
rename to resume/model/order-experience-stages.ts
index 1196767..1c6624d 100644
--- a/src/features/resume/model/order-experience-stages.ts
+++ b/resume/model/order-experience-stages.ts
@@ -1,4 +1,4 @@
-import type { ExperienceStage } from '@/domains/resume/model/types';
+import type { ExperienceStage } from '@/resume/model/types';
const STAGE_PRIORITY: Record = {
problem: 0,
diff --git a/src/features/resume/model/resume-data.ts b/resume/model/resume-data.ts
similarity index 99%
rename from src/features/resume/model/resume-data.ts
rename to resume/model/resume-data.ts
index 35813c3..060b130 100644
--- a/src/features/resume/model/resume-data.ts
+++ b/resume/model/resume-data.ts
@@ -5,7 +5,7 @@ import type {
Education,
Activity,
Certification,
-} from '@/domains/resume/model/types';
+} from '@/resume/model/types';
export const personalInfo: PersonalInfo = {
name: '박은우',
diff --git a/src/domains/resume/model/types.ts b/resume/model/types.ts
similarity index 100%
rename from src/domains/resume/model/types.ts
rename to resume/model/types.ts
diff --git a/src/features/resume/ui/pages/ResumePage.tsx b/resume/ui/pages/ResumePage.tsx
similarity index 99%
rename from src/features/resume/ui/pages/ResumePage.tsx
rename to resume/ui/pages/ResumePage.tsx
index b3a306f..84d5f0f 100644
--- a/src/features/resume/ui/pages/ResumePage.tsx
+++ b/resume/ui/pages/ResumePage.tsx
@@ -8,8 +8,8 @@ import {
experiences,
personalInfo,
personalProjects,
-} from '@/features/resume/model/resume-data';
-import { orderExperienceStages } from '@/features/resume/model/order-experience-stages';
+} from '@/resume/model/resume-data';
+import { orderExperienceStages } from '@/resume/model/order-experience-stages';
export const metadata: Metadata = {
title: 'CV',
diff --git a/search/index.ts b/search/index.ts
new file mode 100644
index 0000000..3f6c64f
--- /dev/null
+++ b/search/index.ts
@@ -0,0 +1,2 @@
+export { getSearchActions } from './model/get-search-actions';
+export { getRecommendedSearchTerms } from './model/search-recommendations';
diff --git a/src/features/search/model/get-search-actions.test.ts b/search/model/get-search-actions.test.ts
similarity index 98%
rename from src/features/search/model/get-search-actions.test.ts
rename to search/model/get-search-actions.test.ts
index 31ead52..31345d1 100644
--- a/src/features/search/model/get-search-actions.test.ts
+++ b/search/model/get-search-actions.test.ts
@@ -3,7 +3,7 @@ import { getSearchActions } from './get-search-actions';
const mockTrackEvent = vi.fn();
-vi.mock('@/shared/analytics/lib/analytics', () => ({
+vi.mock('@/platform/analytics/lib/analytics', () => ({
AnalyticsEvents: {
click: 'click',
theme: 'theme',
diff --git a/src/features/search/model/get-search-actions.ts b/search/model/get-search-actions.ts
similarity index 94%
rename from src/features/search/model/get-search-actions.ts
rename to search/model/get-search-actions.ts
index f740e4b..39c15bf 100644
--- a/src/features/search/model/get-search-actions.ts
+++ b/search/model/get-search-actions.ts
@@ -1,6 +1,6 @@
import { Action } from 'kbar';
-import { FeedData } from '@/domains/post/model/types';
-import { AnalyticsEvents, trackEvent } from '@/shared/analytics/lib/analytics';
+import { FeedData } from '@/blog/model/types';
+import { AnalyticsEvents, trackEvent } from '@/platform/analytics/lib/analytics';
type SearchablePost = Pick<
FeedData,
diff --git a/src/features/search/model/search-recommendations.test.ts b/search/model/search-recommendations.test.ts
similarity index 100%
rename from src/features/search/model/search-recommendations.test.ts
rename to search/model/search-recommendations.test.ts
diff --git a/src/features/search/model/search-recommendations.ts b/search/model/search-recommendations.ts
similarity index 95%
rename from src/features/search/model/search-recommendations.ts
rename to search/model/search-recommendations.ts
index cdac057..2fa96d7 100644
--- a/src/features/search/model/search-recommendations.ts
+++ b/search/model/search-recommendations.ts
@@ -1,4 +1,4 @@
-import type { FeedData } from '@/domains/post/model/types';
+import type { FeedData } from '@/blog/model/types';
type SearchRecommendationSource = Pick;
diff --git a/src/features/search/ui/components/CommandPalette/CommandPalette.module.css b/search/ui/components/CommandPalette/CommandPalette.module.css
similarity index 100%
rename from src/features/search/ui/components/CommandPalette/CommandPalette.module.css
rename to search/ui/components/CommandPalette/CommandPalette.module.css
diff --git a/src/features/search/ui/components/CommandPalette/CommandPalette.tsx b/search/ui/components/CommandPalette/CommandPalette.tsx
similarity index 96%
rename from src/features/search/ui/components/CommandPalette/CommandPalette.tsx
rename to search/ui/components/CommandPalette/CommandPalette.tsx
index 3c81330..93d222f 100644
--- a/src/features/search/ui/components/CommandPalette/CommandPalette.tsx
+++ b/search/ui/components/CommandPalette/CommandPalette.tsx
@@ -11,9 +11,9 @@ import {
useMatches,
} from 'kbar';
import styles from './CommandPalette.module.css';
-import type { FeedData } from '@/domains/post/model/types';
-import { AnalyticsEvents, trackEvent } from '@/shared/analytics/lib/analytics';
-import { getRecommendedSearchTerms } from '@/features/search/model/search-recommendations';
+import type { FeedData } from '@/blog/model/types';
+import { AnalyticsEvents, trackEvent } from '@/platform/analytics/lib/analytics';
+import { getRecommendedSearchTerms } from '@/search/model/search-recommendations';
type SearchScopeId = 'all' | 'tech' | 'life' | 'resume';
diff --git a/src/features/search/ui/components/CommandPalette/index.ts b/search/ui/components/CommandPalette/index.ts
similarity index 100%
rename from src/features/search/ui/components/CommandPalette/index.ts
rename to search/ui/components/CommandPalette/index.ts
diff --git a/src/features/search/ui/components/KBarProvider.tsx b/search/ui/components/KBarProvider.tsx
similarity index 69%
rename from src/features/search/ui/components/KBarProvider.tsx
rename to search/ui/components/KBarProvider.tsx
index ea712c8..817983a 100644
--- a/src/features/search/ui/components/KBarProvider.tsx
+++ b/search/ui/components/KBarProvider.tsx
@@ -1,10 +1,10 @@
'use client';
import { KBarProvider as KBarProviderLib } from 'kbar';
-import { CommandPalette } from '@/features/search/ui/components/CommandPalette';
-import { getSearchActions } from '@/features/search/model/get-search-actions';
+import { CommandPalette } from '@/search/ui/components/CommandPalette';
+import { getSearchActions } from '@/search/model/get-search-actions';
import { useMemo, type ReactNode } from 'react';
-import type { FeedData } from '@/domains/post/model/types';
+import type { FeedData } from '@/blog/model/types';
interface KBarProviderProps {
children: ReactNode;
diff --git a/shared/index.ts b/shared/index.ts
new file mode 100644
index 0000000..b76d748
--- /dev/null
+++ b/shared/index.ts
@@ -0,0 +1,2 @@
+export { Container, type ContainerProps } from './layout';
+export { Button, EmptyState, RouteError } from './ui';
diff --git a/src/shared/layout/Container/Container.test.tsx b/shared/layout/Container/Container.test.tsx
similarity index 100%
rename from src/shared/layout/Container/Container.test.tsx
rename to shared/layout/Container/Container.test.tsx
diff --git a/src/shared/layout/Container/Container.tsx b/shared/layout/Container/Container.tsx
similarity index 100%
rename from src/shared/layout/Container/Container.tsx
rename to shared/layout/Container/Container.tsx
diff --git a/src/shared/layout/Container/index.ts b/shared/layout/Container/index.ts
similarity index 100%
rename from src/shared/layout/Container/index.ts
rename to shared/layout/Container/index.ts
diff --git a/shared/layout/index.ts b/shared/layout/index.ts
new file mode 100644
index 0000000..7719956
--- /dev/null
+++ b/shared/layout/index.ts
@@ -0,0 +1,2 @@
+export { Container } from './Container';
+export type { ContainerProps } from './Container';
diff --git a/src/shared/motion/model/motion-mode.test.ts b/shared/motion/model/motion-mode.test.ts
similarity index 100%
rename from src/shared/motion/model/motion-mode.test.ts
rename to shared/motion/model/motion-mode.test.ts
diff --git a/src/shared/motion/model/motion-mode.ts b/shared/motion/model/motion-mode.ts
similarity index 100%
rename from src/shared/motion/model/motion-mode.ts
rename to shared/motion/model/motion-mode.ts
diff --git a/src/shared/motion/ui/StaggerReveal.tsx b/shared/motion/ui/StaggerReveal.tsx
similarity index 100%
rename from src/shared/motion/ui/StaggerReveal.tsx
rename to shared/motion/ui/StaggerReveal.tsx
diff --git a/src/shared/testing/dom-mocks.ts b/shared/testing/dom-mocks.ts
similarity index 100%
rename from src/shared/testing/dom-mocks.ts
rename to shared/testing/dom-mocks.ts
diff --git a/src/shared/testing/server-only.ts b/shared/testing/server-only.ts
similarity index 100%
rename from src/shared/testing/server-only.ts
rename to shared/testing/server-only.ts
diff --git a/src/shared/ui/Button.test.tsx b/shared/ui/Button.test.tsx
similarity index 100%
rename from src/shared/ui/Button.test.tsx
rename to shared/ui/Button.test.tsx
diff --git a/src/shared/ui/Button/Button.tsx b/shared/ui/Button/Button.tsx
similarity index 100%
rename from src/shared/ui/Button/Button.tsx
rename to shared/ui/Button/Button.tsx
diff --git a/src/shared/ui/Button/Button.types.ts b/shared/ui/Button/Button.types.ts
similarity index 100%
rename from src/shared/ui/Button/Button.types.ts
rename to shared/ui/Button/Button.types.ts
diff --git a/src/shared/ui/Button/index.ts b/shared/ui/Button/index.ts
similarity index 100%
rename from src/shared/ui/Button/index.ts
rename to shared/ui/Button/index.ts
diff --git a/src/shared/ui/EmptyState.test.tsx b/shared/ui/EmptyState.test.tsx
similarity index 100%
rename from src/shared/ui/EmptyState.test.tsx
rename to shared/ui/EmptyState.test.tsx
diff --git a/src/shared/ui/EmptyState/EmptyState.tsx b/shared/ui/EmptyState/EmptyState.tsx
similarity index 100%
rename from src/shared/ui/EmptyState/EmptyState.tsx
rename to shared/ui/EmptyState/EmptyState.tsx
diff --git a/src/shared/ui/EmptyState/index.ts b/shared/ui/EmptyState/index.ts
similarity index 100%
rename from src/shared/ui/EmptyState/index.ts
rename to shared/ui/EmptyState/index.ts
diff --git a/src/shared/ui/RouteError.test.tsx b/shared/ui/RouteError.test.tsx
similarity index 100%
rename from src/shared/ui/RouteError.test.tsx
rename to shared/ui/RouteError.test.tsx
diff --git a/src/shared/ui/RouteState/RouteError.tsx b/shared/ui/RouteState/RouteError.tsx
similarity index 100%
rename from src/shared/ui/RouteState/RouteError.tsx
rename to shared/ui/RouteState/RouteError.tsx
diff --git a/src/shared/ui/RouteState/index.ts b/shared/ui/RouteState/index.ts
similarity index 100%
rename from src/shared/ui/RouteState/index.ts
rename to shared/ui/RouteState/index.ts
diff --git a/src/shared/ui/ThemeToggle.test.tsx b/shared/ui/ThemeToggle.test.tsx
similarity index 98%
rename from src/shared/ui/ThemeToggle.test.tsx
rename to shared/ui/ThemeToggle.test.tsx
index 607437e..b5f1c35 100644
--- a/src/shared/ui/ThemeToggle.test.tsx
+++ b/shared/ui/ThemeToggle.test.tsx
@@ -57,7 +57,7 @@ vi.mock('@/shared/motion/model/motion-mode', () => ({
useEffectiveMotionMode: () => 'full',
}));
-vi.mock('@/shared/analytics/lib/analytics', () => ({
+vi.mock('@/platform/analytics/lib/analytics', () => ({
AnalyticsEvents: {
click: 'click',
theme: 'theme',
diff --git a/src/shared/ui/ThemeToggle.tsx b/shared/ui/ThemeToggle.tsx
similarity index 98%
rename from src/shared/ui/ThemeToggle.tsx
rename to shared/ui/ThemeToggle.tsx
index f3a2a85..9f181e9 100644
--- a/src/shared/ui/ThemeToggle.tsx
+++ b/shared/ui/ThemeToggle.tsx
@@ -3,7 +3,7 @@
import { useTheme } from 'next-themes';
import { useEffect, useState } from 'react';
import { motion } from 'framer-motion';
-import { AnalyticsEvents, trackEvent } from '@/shared/analytics/lib/analytics';
+import { AnalyticsEvents, trackEvent } from '@/platform/analytics/lib/analytics';
import { useEffectiveMotionMode } from '@/shared/motion/model/motion-mode';
export const THEME_TRANSITION_ORIGIN_EVENT = 'theme-transition-origin';
diff --git a/src/shared/ui/ThemeTransitionWash.tsx b/shared/ui/ThemeTransitionWash.tsx
similarity index 100%
rename from src/shared/ui/ThemeTransitionWash.tsx
rename to shared/ui/ThemeTransitionWash.tsx
diff --git a/src/shared/ui/icons/AppSectionIcon.tsx b/shared/ui/icons/AppSectionIcon.tsx
similarity index 100%
rename from src/shared/ui/icons/AppSectionIcon.tsx
rename to shared/ui/icons/AppSectionIcon.tsx
diff --git a/src/shared/ui/index.ts b/shared/ui/index.ts
similarity index 100%
rename from src/shared/ui/index.ts
rename to shared/ui/index.ts
diff --git a/src/components/visualization/BinarySearchVisualization.tsx b/shared/visualization/BinarySearchVisualization.tsx
similarity index 100%
rename from src/components/visualization/BinarySearchVisualization.tsx
rename to shared/visualization/BinarySearchVisualization.tsx
diff --git a/src/components/visualization/DPVisualization.tsx b/shared/visualization/DPVisualization.tsx
similarity index 100%
rename from src/components/visualization/DPVisualization.tsx
rename to shared/visualization/DPVisualization.tsx
diff --git a/src/components/visualization/GraphTraversalVisualization.tsx b/shared/visualization/GraphTraversalVisualization.tsx
similarity index 100%
rename from src/components/visualization/GraphTraversalVisualization.tsx
rename to shared/visualization/GraphTraversalVisualization.tsx
diff --git a/src/components/visualization/SlidingWindowVisualization.tsx b/shared/visualization/SlidingWindowVisualization.tsx
similarity index 100%
rename from src/components/visualization/SlidingWindowVisualization.tsx
rename to shared/visualization/SlidingWindowVisualization.tsx
diff --git a/src/components/visualization/SortingVisualization.tsx b/shared/visualization/SortingVisualization.tsx
similarity index 100%
rename from src/components/visualization/SortingVisualization.tsx
rename to shared/visualization/SortingVisualization.tsx
diff --git a/src/components/visualization/TwoPointerVisualization.tsx b/shared/visualization/TwoPointerVisualization.tsx
similarity index 100%
rename from src/components/visualization/TwoPointerVisualization.tsx
rename to shared/visualization/TwoPointerVisualization.tsx
diff --git a/src/components/visualization/index.ts b/shared/visualization/index.ts
similarity index 100%
rename from src/components/visualization/index.ts
rename to shared/visualization/index.ts
diff --git a/src/core/config/site.ts b/site/config/site.ts
similarity index 100%
rename from src/core/config/site.ts
rename to site/config/site.ts
diff --git a/src/shared/layout/Footer/Footer.test.tsx b/site/footer/Footer/Footer.test.tsx
similarity index 100%
rename from src/shared/layout/Footer/Footer.test.tsx
rename to site/footer/Footer/Footer.test.tsx
diff --git a/src/shared/layout/Footer/Footer.tsx b/site/footer/Footer/Footer.tsx
similarity index 100%
rename from src/shared/layout/Footer/Footer.tsx
rename to site/footer/Footer/Footer.tsx
diff --git a/src/shared/layout/Footer/index.ts b/site/footer/Footer/index.ts
similarity index 100%
rename from src/shared/layout/Footer/index.ts
rename to site/footer/Footer/index.ts
diff --git a/src/features/home/model/home-feed.test.ts b/site/home/model/home-feed.test.ts
similarity index 97%
rename from src/features/home/model/home-feed.test.ts
rename to site/home/model/home-feed.test.ts
index 6fef3e0..06e3192 100644
--- a/src/features/home/model/home-feed.test.ts
+++ b/site/home/model/home-feed.test.ts
@@ -1,5 +1,5 @@
import { describe, expect, it } from 'vitest';
-import type { FeedData } from '@/domains/post/model/types';
+import type { FeedData } from '@/blog/model/types';
import {
buildHomeCategoryCounts,
filterHomePosts,
diff --git a/src/features/home/model/home-feed.ts b/site/home/model/home-feed.ts
similarity index 92%
rename from src/features/home/model/home-feed.ts
rename to site/home/model/home-feed.ts
index 3876b3f..5c44942 100644
--- a/src/features/home/model/home-feed.ts
+++ b/site/home/model/home-feed.ts
@@ -1,5 +1,5 @@
-import type { FeedData } from '@/domains/post/model/types';
-import type { Category } from '@/features/blog/ui/components/CategoryFilter';
+import type { FeedData } from '@/blog/model/types';
+import type { Category } from '@/blog/ui/components/CategoryFilter';
export interface HomePopularView {
slug: string;
diff --git a/src/features/home/ui/pages/HomePage.test.tsx b/site/home/ui/pages/HomePage.test.tsx
similarity index 92%
rename from src/features/home/ui/pages/HomePage.test.tsx
rename to site/home/ui/pages/HomePage.test.tsx
index 12db379..5045e12 100644
--- a/src/features/home/ui/pages/HomePage.test.tsx
+++ b/site/home/ui/pages/HomePage.test.tsx
@@ -1,16 +1,16 @@
import { render, screen } from '@testing-library/react';
import { describe, expect, it, vi } from 'vitest';
-import type { FeedData } from '@/domains/post/model/types';
+import type { FeedData } from '@/blog/model/types';
import HomePage from './HomePage';
const mockGetSortedFeedData = vi.fn<() => FeedData[]>(() => []);
const mockGetPopularViewsInRecentDays = vi.fn(async () => []);
-vi.mock('@/features/blog/services/post-repository', () => ({
+vi.mock('@/blog/services/post-repository', () => ({
getSortedFeedData: () => mockGetSortedFeedData(),
}));
-vi.mock('@/app/actions/view', () => ({
+vi.mock('@/blog/api/view', () => ({
getPopularViewsInRecentDays: (...args: unknown[]) =>
mockGetPopularViewsInRecentDays(...args),
}));
diff --git a/src/features/home/ui/pages/HomePage.tsx b/site/home/ui/pages/HomePage.tsx
similarity index 77%
rename from src/features/home/ui/pages/HomePage.tsx
rename to site/home/ui/pages/HomePage.tsx
index dac4ab1..67c5905 100644
--- a/src/features/home/ui/pages/HomePage.tsx
+++ b/site/home/ui/pages/HomePage.tsx
@@ -1,5 +1,5 @@
-import { getPopularViewsInRecentDays } from '@/app/actions/view';
-import { getSortedFeedData } from '@/features/blog/services/post-repository';
+import { getPopularViewsInRecentDays } from '@/blog/api/view';
+import { getSortedFeedData } from '@/blog/services/post-repository';
import HomePageClient from './HomePageClient';
const POPULAR_VIEW_DAYS = 30;
diff --git a/src/features/home/ui/pages/HomePageClient.test.tsx b/site/home/ui/pages/HomePageClient.test.tsx
similarity index 93%
rename from src/features/home/ui/pages/HomePageClient.test.tsx
rename to site/home/ui/pages/HomePageClient.test.tsx
index 407f9a6..af131ed 100644
--- a/src/features/home/ui/pages/HomePageClient.test.tsx
+++ b/site/home/ui/pages/HomePageClient.test.tsx
@@ -1,7 +1,7 @@
import { render, screen } from '@testing-library/react';
import type { ReactNode } from 'react';
import { describe, expect, it, vi } from 'vitest';
-import type { FeedData } from '@/domains/post/model/types';
+import type { FeedData } from '@/blog/model/types';
import HomePageClient from './HomePageClient';
vi.mock('@/shared/layout', () => ({
@@ -14,7 +14,7 @@ vi.mock('@/shared/layout', () => ({
}) => {children}
,
}));
-vi.mock('@/features/blog/ui/components', () => ({
+vi.mock('@/blog/ui/components', () => ({
CategoryFilter: ({
categories,
activeCategory,
diff --git a/src/features/home/ui/pages/HomePageClient.tsx b/site/home/ui/pages/HomePageClient.tsx
similarity index 95%
rename from src/features/home/ui/pages/HomePageClient.tsx
rename to site/home/ui/pages/HomePageClient.tsx
index 0d1b9e9..fd73608 100644
--- a/src/features/home/ui/pages/HomePageClient.tsx
+++ b/site/home/ui/pages/HomePageClient.tsx
@@ -2,18 +2,18 @@
import { useMemo, useState } from 'react';
import { clsx } from 'clsx';
-import type { FeedData } from '@/domains/post/model/types';
+import type { FeedData } from '@/blog/model/types';
import {
CategoryFilter,
PostList,
type Category,
-} from '@/features/blog/ui/components';
+} from '@/blog/ui/components';
import {
buildHomeCategoryCounts,
filterHomePosts,
type HomePopularView,
type HomeSortOrder,
-} from '@/features/home/model/home-feed';
+} from '@/site/home/model/home-feed';
import { Container } from '@/shared/layout';
interface HomePageClientProps {
diff --git a/site/index.ts b/site/index.ts
new file mode 100644
index 0000000..fdaaa64
--- /dev/null
+++ b/site/index.ts
@@ -0,0 +1,8 @@
+export { AppShell } from './shell/AppShell';
+export { default as AppProviders } from './providers/AppProviders';
+export {
+ SITE_AUTHOR,
+ SITE_DESCRIPTION,
+ SITE_NAME,
+ SITE_URL,
+} from './config/site';
diff --git a/src/shared/layout/PageTransition.tsx b/site/layout/PageTransition.tsx
similarity index 100%
rename from src/shared/layout/PageTransition.tsx
rename to site/layout/PageTransition.tsx
diff --git a/src/shared/layout/Header/MobileBottomNav.test.tsx b/site/navigation/MobileBottomNav.test.tsx
similarity index 98%
rename from src/shared/layout/Header/MobileBottomNav.test.tsx
rename to site/navigation/MobileBottomNav.test.tsx
index e9de5bd..817297a 100644
--- a/src/shared/layout/Header/MobileBottomNav.test.tsx
+++ b/site/navigation/MobileBottomNav.test.tsx
@@ -20,7 +20,7 @@ vi.mock('next/link', () => ({
),
}));
-vi.mock('@/shared/analytics/lib/analytics', () => {
+vi.mock('@/platform/analytics/lib/analytics', () => {
return {
AnalyticsEvents: {
click: 'click',
diff --git a/src/shared/layout/Header/MobileBottomNav.tsx b/site/navigation/MobileBottomNav.tsx
similarity index 99%
rename from src/shared/layout/Header/MobileBottomNav.tsx
rename to site/navigation/MobileBottomNav.tsx
index 91a2778..8c46cbb 100644
--- a/src/shared/layout/Header/MobileBottomNav.tsx
+++ b/site/navigation/MobileBottomNav.tsx
@@ -3,7 +3,7 @@
import { useEffect, useMemo } from 'react';
import Link from 'next/link';
import { clsx } from 'clsx';
-import { AnalyticsEvents, trackEvent } from '@/shared/analytics/lib/analytics';
+import { AnalyticsEvents, trackEvent } from '@/platform/analytics/lib/analytics';
import { AppSectionIcon } from '@/shared/ui/icons/AppSectionIcon';
interface MobileBottomNavProps {
diff --git a/src/core/providers/AppProviders.tsx b/site/providers/AppProviders.tsx
similarity index 62%
rename from src/core/providers/AppProviders.tsx
rename to site/providers/AppProviders.tsx
index 6eb08a9..035bf93 100644
--- a/src/core/providers/AppProviders.tsx
+++ b/site/providers/AppProviders.tsx
@@ -1,11 +1,11 @@
import type { ReactNode } from 'react';
-import ThemeProvider from '@/shared/providers/ThemeProvider';
-import KBarProvider from '@/features/search/ui/components/KBarProvider';
-import UmamiAnalytics from '@/shared/analytics/components/UmamiAnalytics';
-import { getSortedFeedData } from '@/features/blog/services/post-repository';
-import JsonLd from '@/shared/seo/JsonLd';
-import { SITE_AUTHOR, SITE_NAME, SITE_URL } from '@/core/config/site';
-import AgentationOverlay from '@/shared/devtools/AgentationOverlay';
+import ThemeProvider from '@/site/providers/ThemeProvider';
+import KBarProvider from '@/search/ui/components/KBarProvider';
+import UmamiAnalytics from '@/platform/analytics/components/UmamiAnalytics';
+import { getSortedFeedData } from '@/blog/services/post-repository';
+import JsonLd from '@/platform/seo/JsonLd';
+import { SITE_AUTHOR, SITE_NAME, SITE_URL } from '@/site/config/site';
+import AgentationOverlay from '@/platform/devtools/AgentationOverlay';
interface AppProvidersProps {
children: ReactNode;
diff --git a/src/shared/providers/ThemeProvider.tsx b/site/providers/ThemeProvider.tsx
similarity index 100%
rename from src/shared/providers/ThemeProvider.tsx
rename to site/providers/ThemeProvider.tsx
diff --git a/src/shared/layout/AppShell/AppShell.tsx b/site/shell/AppShell/AppShell.tsx
similarity index 98%
rename from src/shared/layout/AppShell/AppShell.tsx
rename to site/shell/AppShell/AppShell.tsx
index 13a1f4c..a53bf65 100644
--- a/src/shared/layout/AppShell/AppShell.tsx
+++ b/site/shell/AppShell/AppShell.tsx
@@ -6,9 +6,9 @@ import type { ReactNode } from 'react';
import { useEffect, useMemo, useState } from 'react';
import { useKBar } from 'kbar';
import { clsx } from 'clsx';
-import type { FeedData } from '@/domains/post/model/types';
-import { personalInfo } from '@/features/resume/model/resume-data';
-import MobileBottomNav from '@/shared/layout/Header/MobileBottomNav';
+import type { FeedData } from '@/blog/model/types';
+import { personalInfo } from '@/resume/model/resume-data';
+import MobileBottomNav from '@/site/navigation/MobileBottomNav';
import { AppSectionIcon } from '@/shared/ui/icons/AppSectionIcon';
import ThemeToggle from '@/shared/ui/ThemeToggle';
import ThemeTransitionWash from '@/shared/ui/ThemeTransitionWash';
diff --git a/src/shared/layout/AppShell/index.ts b/site/shell/AppShell/index.ts
similarity index 100%
rename from src/shared/layout/AppShell/index.ts
rename to site/shell/AppShell/index.ts
diff --git a/src/app/blog/[slug]/error.tsx b/src/app/blog/[slug]/error.tsx
index b0d6106..f80a81e 100644
--- a/src/app/blog/[slug]/error.tsx
+++ b/src/app/blog/[slug]/error.tsx
@@ -2,7 +2,7 @@
import { useEffect } from 'react';
import { RouteError } from '@/shared/ui';
-import { AnalyticsEvents, trackEvent } from '@/shared/analytics/lib/analytics';
+import { AnalyticsEvents, trackEvent } from '@/platform/analytics/lib/analytics';
interface BlogPostErrorProps {
error: Error & { digest?: string };
diff --git a/src/app/blog/[slug]/page.tsx b/src/app/blog/[slug]/page.tsx
index ec1ccc6..108ccca 100644
--- a/src/app/blog/[slug]/page.tsx
+++ b/src/app/blog/[slug]/page.tsx
@@ -2,4 +2,4 @@ export {
generateStaticParams,
generateMetadata,
default,
-} from '@/features/blog/ui/pages/BlogPostPage';
+} from '@/blog/ui/pages/BlogPostPage';
diff --git a/src/app/blog/error.tsx b/src/app/blog/error.tsx
index 836502c..87e75f4 100644
--- a/src/app/blog/error.tsx
+++ b/src/app/blog/error.tsx
@@ -2,7 +2,7 @@
import { useEffect } from 'react';
import { RouteError } from '@/shared/ui';
-import { AnalyticsEvents, trackEvent } from '@/shared/analytics/lib/analytics';
+import { AnalyticsEvents, trackEvent } from '@/platform/analytics/lib/analytics';
interface BlogErrorProps {
error: Error & { digest?: string };
diff --git a/src/app/blog/page.tsx b/src/app/blog/page.tsx
index d3a577c..f69e5ed 100644
--- a/src/app/blog/page.tsx
+++ b/src/app/blog/page.tsx
@@ -1 +1 @@
-export { metadata, default } from '@/features/blog/ui/pages/BlogListPage';
+export { metadata, default } from '@/blog/ui/pages/BlogListPage';
diff --git a/src/app/engineering/page.tsx b/src/app/engineering/page.tsx
index d2db93c..532ec69 100644
--- a/src/app/engineering/page.tsx
+++ b/src/app/engineering/page.tsx
@@ -1 +1 @@
-export { metadata, default } from '@/features/blog/ui/pages/EngineeringPage';
+export { metadata, default } from '@/blog/ui/pages/EngineeringPage';
diff --git a/src/app/engineering/series/[seriesId]/page.tsx b/src/app/engineering/series/[seriesId]/page.tsx
index a1634ce..68275c1 100644
--- a/src/app/engineering/series/[seriesId]/page.tsx
+++ b/src/app/engineering/series/[seriesId]/page.tsx
@@ -1,4 +1,4 @@
-import EngineeringSeriesPage from '@/features/blog/ui/pages/EngineeringSeriesPage';
+import EngineeringSeriesPage from '@/blog/ui/pages/EngineeringSeriesPage';
export default async function EngineeringSeriesRoutePage({
params,
diff --git a/src/app/error.tsx b/src/app/error.tsx
index c4d2eca..17eefd0 100644
--- a/src/app/error.tsx
+++ b/src/app/error.tsx
@@ -2,7 +2,7 @@
import { useEffect } from 'react';
import { RouteError } from '@/shared/ui';
-import { AnalyticsEvents, trackEvent } from '@/shared/analytics/lib/analytics';
+import { AnalyticsEvents, trackEvent } from '@/platform/analytics/lib/analytics';
interface RootErrorProps {
error: Error & { digest?: string };
diff --git a/src/app/feed.xml/route.test.ts b/src/app/feed.xml/route.test.ts
index 47fd9c5..603d98e 100644
--- a/src/app/feed.xml/route.test.ts
+++ b/src/app/feed.xml/route.test.ts
@@ -1,7 +1,7 @@
// @vitest-environment node
import { describe, expect, it } from 'vitest';
-import { getSortedFeedData } from '@/features/blog/services/post-repository';
+import { getSortedFeedData } from '@/blog/services/post-repository';
import { GET } from './route';
describe('/feed.xml', () => {
diff --git a/src/app/feed.xml/route.ts b/src/app/feed.xml/route.ts
index 6dba13c..7d5cc0a 100644
--- a/src/app/feed.xml/route.ts
+++ b/src/app/feed.xml/route.ts
@@ -1,4 +1,4 @@
-import { getSortedFeedData } from '@/features/blog/services/post-repository';
+import { getSortedFeedData } from '@/blog/services/post-repository';
const SITE_URL = 'https://eunu-log.vercel.app';
diff --git a/src/app/layout.tsx b/src/app/layout.tsx
index 6f83731..034d7e7 100644
--- a/src/app/layout.tsx
+++ b/src/app/layout.tsx
@@ -3,15 +3,15 @@ import type { ReactNode } from 'react';
import '@/styles/globals.css';
import '@/styles/tossface.css';
-import AppProviders from '@/core/providers/AppProviders';
-import { getSortedFeedData } from '@/features/blog/services/post-repository';
-import { AppShell } from '@/shared/layout';
+import AppProviders from '@/site/providers/AppProviders';
+import { getSortedFeedData } from '@/blog/services/post-repository';
+import { AppShell } from '@/site/shell/AppShell';
import {
SITE_AUTHOR,
SITE_DESCRIPTION,
SITE_NAME,
SITE_URL,
-} from '@/core/config/site';
+} from '@/site/config/site';
export const metadata: Metadata = {
metadataBase: new URL(SITE_URL),
diff --git a/src/app/life/page.tsx b/src/app/life/page.tsx
index b05e910..ba07a64 100644
--- a/src/app/life/page.tsx
+++ b/src/app/life/page.tsx
@@ -1 +1 @@
-export { metadata, default } from '@/features/blog/ui/pages/LifePage';
+export { metadata, default } from '@/blog/ui/pages/LifePage';
diff --git a/src/app/page.tsx b/src/app/page.tsx
index 7c24c78..5a40793 100644
--- a/src/app/page.tsx
+++ b/src/app/page.tsx
@@ -1 +1 @@
-export { default } from '@/features/home/ui/pages/HomePage';
+export { default } from '@/site/home/ui/pages/HomePage';
diff --git a/src/app/resume/error.tsx b/src/app/resume/error.tsx
index ad96db6..c416587 100644
--- a/src/app/resume/error.tsx
+++ b/src/app/resume/error.tsx
@@ -2,7 +2,7 @@
import { useEffect } from 'react';
import { RouteError } from '@/shared/ui';
-import { AnalyticsEvents, trackEvent } from '@/shared/analytics/lib/analytics';
+import { AnalyticsEvents, trackEvent } from '@/platform/analytics/lib/analytics';
interface ResumeErrorProps {
error: Error & { digest?: string };
diff --git a/src/app/resume/page.tsx b/src/app/resume/page.tsx
index 1d7b66a..e55fd76 100644
--- a/src/app/resume/page.tsx
+++ b/src/app/resume/page.tsx
@@ -1 +1 @@
-export { metadata, default } from '@/features/resume/ui/pages/ResumePage';
+export { metadata, default } from '@/resume/ui/pages/ResumePage';
diff --git a/src/app/series/page.tsx b/src/app/series/page.tsx
index 06efe8f..58d42ed 100644
--- a/src/app/series/page.tsx
+++ b/src/app/series/page.tsx
@@ -1 +1 @@
-export { metadata, default } from '@/features/blog/ui/pages/SeriesPage';
+export { metadata, default } from '@/blog/ui/pages/SeriesPage';
diff --git a/src/app/sitemap.test.ts b/src/app/sitemap.test.ts
index 23326bb..e5453a9 100644
--- a/src/app/sitemap.test.ts
+++ b/src/app/sitemap.test.ts
@@ -1,7 +1,7 @@
// @vitest-environment node
import { describe, expect, it } from 'vitest';
-import { getSortedFeedData } from '@/features/blog/services/post-repository';
+import { getSortedFeedData } from '@/blog/services/post-repository';
import sitemap from './sitemap';
const URL = 'https://eunu-log.vercel.app';
diff --git a/src/app/sitemap.ts b/src/app/sitemap.ts
index 58f1ef3..2c7b700 100644
--- a/src/app/sitemap.ts
+++ b/src/app/sitemap.ts
@@ -1,5 +1,5 @@
import { MetadataRoute } from 'next';
-import { getSortedFeedData } from '@/features/blog/services/post-repository';
+import { getSortedFeedData } from '@/blog/services/post-repository';
const URL = 'https://eunu-log.vercel.app';
diff --git a/src/app/template.tsx b/src/app/template.tsx
index b95b983..2079614 100644
--- a/src/app/template.tsx
+++ b/src/app/template.tsx
@@ -1,4 +1,4 @@
-import PageTransition from '@/shared/layout/PageTransition';
+import PageTransition from '@/site/layout/PageTransition';
export default function Template({ children }: { children: React.ReactNode }) {
return {children};
diff --git a/src/shared/layout/index.ts b/src/shared/layout/index.ts
deleted file mode 100644
index 0502b4d..0000000
--- a/src/shared/layout/index.ts
+++ /dev/null
@@ -1,5 +0,0 @@
-// Layout Components
-export { Footer } from './Footer';
-export { Container } from './Container';
-export { AppShell } from './AppShell';
-export type { ContainerProps } from './Container';
diff --git a/src/styles/globals.css b/styles/globals.css
similarity index 100%
rename from src/styles/globals.css
rename to styles/globals.css
diff --git a/src/styles/globals.test.ts b/styles/globals.test.ts
similarity index 92%
rename from src/styles/globals.test.ts
rename to styles/globals.test.ts
index 1fc0baf..23b23ab 100644
--- a/src/styles/globals.test.ts
+++ b/styles/globals.test.ts
@@ -2,9 +2,9 @@ import fs from 'node:fs';
import path from 'node:path';
import { describe, expect, it } from 'vitest';
-const globalsPath = path.resolve(process.cwd(), 'src/styles/globals.css');
+const globalsPath = path.resolve(process.cwd(), 'styles/globals.css');
const globalsContent = fs.readFileSync(globalsPath, 'utf8');
-const tokensPath = path.resolve(process.cwd(), 'src/styles/tokens.css');
+const tokensPath = path.resolve(process.cwd(), 'styles/tokens.css');
const tokensContent = fs.readFileSync(tokensPath, 'utf8');
describe('globals styles', () => {
diff --git a/src/styles/tokens.css b/styles/tokens.css
similarity index 100%
rename from src/styles/tokens.css
rename to styles/tokens.css
diff --git a/src/styles/tokens.test.ts b/styles/tokens.test.ts
similarity index 94%
rename from src/styles/tokens.test.ts
rename to styles/tokens.test.ts
index 19d20a1..d5f763a 100644
--- a/src/styles/tokens.test.ts
+++ b/styles/tokens.test.ts
@@ -2,7 +2,7 @@ import fs from 'node:fs';
import path from 'node:path';
import { describe, expect, it } from 'vitest';
-const tokensPath = path.resolve(process.cwd(), 'src/styles/tokens.css');
+const tokensPath = path.resolve(process.cwd(), 'styles/tokens.css');
const tokensContent = fs.readFileSync(tokensPath, 'utf8');
describe('TDS token definitions', () => {
diff --git a/src/styles/tossface.css b/styles/tossface.css
similarity index 100%
rename from src/styles/tossface.css
rename to styles/tossface.css
diff --git a/internal/config/.markdownlint-cli2.jsonc b/tooling/config/.markdownlint-cli2.jsonc
similarity index 100%
rename from internal/config/.markdownlint-cli2.jsonc
rename to tooling/config/.markdownlint-cli2.jsonc
diff --git a/internal/config/cspell.json b/tooling/config/cspell.json
similarity index 100%
rename from internal/config/cspell.json
rename to tooling/config/cspell.json
diff --git a/internal/scripts/check-css-syntax.mjs b/tooling/scripts/check-css-syntax.mjs
similarity index 72%
rename from internal/scripts/check-css-syntax.mjs
rename to tooling/scripts/check-css-syntax.mjs
index e0651d9..cd5d1be 100644
--- a/internal/scripts/check-css-syntax.mjs
+++ b/tooling/scripts/check-css-syntax.mjs
@@ -3,7 +3,7 @@ import path from 'path';
import postcss from 'postcss';
const ROOT = process.cwd();
-const TARGET_DIR = path.join(ROOT, 'src');
+const TARGET_DIRS = ['src', 'blog', 'search', 'shared', 'site', 'styles'];
async function collectCssFiles(dirPath) {
const entries = await fs.readdir(dirPath, { withFileTypes: true });
@@ -42,10 +42,27 @@ async function validateCssFile(filePath) {
}
async function main() {
- const cssFiles = await collectCssFiles(TARGET_DIR);
+ const existingTargetDirs = await Promise.all(
+ TARGET_DIRS.map(async (targetDir) => {
+ const fullPath = path.join(ROOT, targetDir);
+ try {
+ const stats = await fs.stat(fullPath);
+ return stats.isDirectory() ? fullPath : null;
+ } catch {
+ return null;
+ }
+ })
+ );
+ const cssFiles = (
+ await Promise.all(
+ existingTargetDirs
+ .filter((entry) => entry !== null)
+ .map((targetDir) => collectCssFiles(targetDir))
+ )
+ ).flat();
if (cssFiles.length === 0) {
- console.log('No CSS files found under src/.');
+ console.log('No CSS files found under configured source directories.');
return;
}
diff --git a/internal/scripts/dev-with-agentation.mjs b/tooling/scripts/dev-with-agentation.mjs
similarity index 100%
rename from internal/scripts/dev-with-agentation.mjs
rename to tooling/scripts/dev-with-agentation.mjs
diff --git a/internal/scripts/posts/audit-post-quality.mjs b/tooling/scripts/posts/audit-post-quality.mjs
similarity index 100%
rename from internal/scripts/posts/audit-post-quality.mjs
rename to tooling/scripts/posts/audit-post-quality.mjs
diff --git a/internal/scripts/posts/localize-post-images.mjs b/tooling/scripts/posts/localize-post-images.mjs
similarity index 100%
rename from internal/scripts/posts/localize-post-images.mjs
rename to tooling/scripts/posts/localize-post-images.mjs
diff --git a/internal/scripts/posts/new-post.js b/tooling/scripts/posts/new-post.js
similarity index 100%
rename from internal/scripts/posts/new-post.js
rename to tooling/scripts/posts/new-post.js
diff --git a/internal/scripts/research/build_nekaracuba_corpus.py b/tooling/scripts/research/build_nekaracuba_corpus.py
similarity index 100%
rename from internal/scripts/research/build_nekaracuba_corpus.py
rename to tooling/scripts/research/build_nekaracuba_corpus.py
diff --git a/internal/scripts/research/generate_toss_analysis.py b/tooling/scripts/research/generate_toss_analysis.py
similarity index 100%
rename from internal/scripts/research/generate_toss_analysis.py
rename to tooling/scripts/research/generate_toss_analysis.py
diff --git a/internal/scripts/verify-docs.mjs b/tooling/scripts/verify-docs.mjs
similarity index 97%
rename from internal/scripts/verify-docs.mjs
rename to tooling/scripts/verify-docs.mjs
index 93000be..a2e71bc 100644
--- a/internal/scripts/verify-docs.mjs
+++ b/tooling/scripts/verify-docs.mjs
@@ -88,7 +88,7 @@ function runMarkdownlint() {
path.join(ROOT, 'node_modules', '.bin', 'markdownlint-cli2'),
[
'--config',
- path.join('internal', 'config', '.markdownlint-cli2.jsonc'),
+ path.join('tooling', 'config', '.markdownlint-cli2.jsonc'),
...markdownlintTargets,
],
{
diff --git a/tsconfig.json b/tsconfig.json
index 0f96f2a..bcdde96 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -13,13 +13,14 @@
"isolatedModules": true,
"jsx": "react-jsx",
"incremental": true,
+ "baseUrl": ".",
"plugins": [
{
"name": "next"
}
],
"paths": {
- "@/*": ["./src/*"]
+ "@/*": ["./*"]
}
},
"include": [
diff --git a/vitest.config.ts b/vitest.config.ts
index 769dc89..d812491 100644
--- a/vitest.config.ts
+++ b/vitest.config.ts
@@ -4,30 +4,50 @@ import path from 'path';
export default defineConfig({
plugins: [react()],
- test: {
- environment: 'jsdom',
- globals: true,
- setupFiles: './vitest.setup.ts',
- include: ['src/**/*.test.ts', 'src/**/*.test.tsx'],
+ resolve: {
alias: {
- '@': path.resolve(__dirname, './src'),
+ '@': path.resolve(__dirname, '.'),
'server-only': path.resolve(
__dirname,
- './src/shared/testing/server-only.ts'
+ './shared/testing/server-only.ts'
),
},
+ },
+ test: {
+ environment: 'jsdom',
+ globals: true,
+ setupFiles: './vitest.setup.ts',
+ include: [
+ 'blog/**/*.test.ts',
+ 'blog/**/*.test.tsx',
+ 'platform/**/*.test.ts',
+ 'platform/**/*.test.tsx',
+ 'resume/**/*.test.ts',
+ 'resume/**/*.test.tsx',
+ 'search/**/*.test.ts',
+ 'search/**/*.test.tsx',
+ 'shared/**/*.test.ts',
+ 'shared/**/*.test.tsx',
+ 'site/**/*.test.ts',
+ 'site/**/*.test.tsx',
+ 'src/**/*.test.ts',
+ 'src/**/*.test.tsx',
+ 'styles/**/*.test.ts',
+ ],
coverage: {
provider: 'v8',
include: [
- 'src/core/**/*.{ts,tsx}',
- 'src/domains/**/*.{ts,tsx}',
- 'src/features/**/*.{ts,tsx}',
- 'src/shared/**/*.{ts,tsx}',
- 'src/styles/**/*.{ts,tsx}',
- 'src/components/visualization/**/*.{ts,tsx}',
+ 'blog/**/*.{ts,tsx}',
+ 'platform/**/*.{ts,tsx}',
+ 'resume/**/*.{ts,tsx}',
+ 'search/**/*.{ts,tsx}',
+ 'shared/**/*.{ts,tsx}',
+ 'site/**/*.{ts,tsx}',
+ 'src/app/**/*.{ts,tsx}',
+ 'styles/**/*.{ts,tsx}',
],
exclude: [
- 'src/shared/testing/**',
+ 'shared/testing/**',
'**/*.types.ts',
'**/index.ts',
'**/index.tsx',