mvc-kit 2.13.0 → 2.13.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/BEST_PRACTICES.md +1390 -0
- package/agent-config/claude-code/agents/mvc-kit-architect.md +8 -3
- package/agent-config/claude-code/skills/{guide → mvc-kit}/SKILL.md +10 -1
- package/agent-config/lib/install-claude.mjs +39 -110
- package/examples/primitive/channel.ts +109 -0
- package/examples/primitive/collection.ts +118 -0
- package/examples/primitive/controller.ts +118 -0
- package/examples/primitive/counter.ts +108 -0
- package/examples/primitive/env.d.ts +1 -0
- package/examples/primitive/eventbus.ts +77 -0
- package/examples/primitive/feed.ts +162 -0
- package/examples/primitive/model.ts +82 -0
- package/examples/primitive/pagination.ts +91 -0
- package/examples/primitive/pending.ts +189 -0
- package/examples/primitive/persistent-collection.ts +116 -0
- package/examples/primitive/resource.ts +114 -0
- package/examples/primitive/selection.ts +96 -0
- package/examples/primitive/sorting.ts +112 -0
- package/examples/primitive/timer.ts +58 -0
- package/examples/primitive/trackable.ts +225 -0
- package/examples/primitive/tsconfig.json +20 -0
- package/examples/primitive/viewmodel-service.ts +161 -0
- package/examples/react/AuthExample/index.html +12 -0
- package/examples/react/AuthExample/src/App.tsx +29 -0
- package/examples/react/AuthExample/src/components/AdminPage.tsx +51 -0
- package/examples/react/AuthExample/src/components/AppHeader.tsx +32 -0
- package/examples/react/AuthExample/src/components/AuthGuard.tsx +50 -0
- package/examples/react/AuthExample/src/components/AuthScreen.tsx +181 -0
- package/examples/react/AuthExample/src/components/DashboardPage.tsx +41 -0
- package/examples/react/AuthExample/src/components/ProfilePage.tsx +44 -0
- package/examples/react/AuthExample/src/components/Toast.tsx +41 -0
- package/examples/react/AuthExample/src/env.d.ts +10 -0
- package/examples/react/AuthExample/src/events/AppEventBus.ts +7 -0
- package/examples/react/AuthExample/src/main.tsx +10 -0
- package/examples/react/AuthExample/src/mock/api.ts +78 -0
- package/examples/react/AuthExample/src/models/LoginFormModel.ts +19 -0
- package/examples/react/AuthExample/src/models/RegisterFormModel.ts +25 -0
- package/examples/react/AuthExample/src/services/AuthService.ts +21 -0
- package/examples/react/AuthExample/src/styles.css +445 -0
- package/examples/react/AuthExample/src/types/auth.ts +12 -0
- package/examples/react/AuthExample/src/viewmodels/AuthViewModel.ts +111 -0
- package/examples/react/AuthExample/tsconfig.json +22 -0
- package/examples/react/AuthExample/vite.config.ts +18 -0
- package/examples/react/ComplexApp/index.html +12 -0
- package/examples/react/ComplexApp/src/App.tsx +17 -0
- package/examples/react/ComplexApp/src/channels/ActivityChannel.ts +24 -0
- package/examples/react/ComplexApp/src/channels/DashboardChannel.ts +26 -0
- package/examples/react/ComplexApp/src/channels/ErrorsChannel.ts +5 -0
- package/examples/react/ComplexApp/src/channels/LatencyChannel.ts +5 -0
- package/examples/react/ComplexApp/src/channels/OrdersChannel.ts +5 -0
- package/examples/react/ComplexApp/src/channels/RevenueChannel.ts +5 -0
- package/examples/react/ComplexApp/src/channels/TrafficChannel.ts +5 -0
- package/examples/react/ComplexApp/src/channels/UsersMetricChannel.ts +5 -0
- package/examples/react/ComplexApp/src/collections/DashboardCollection.ts +6 -0
- package/examples/react/ComplexApp/src/collections/ErrorsCollection.ts +3 -0
- package/examples/react/ComplexApp/src/collections/LatencyCollection.ts +3 -0
- package/examples/react/ComplexApp/src/collections/OrdersCollection.ts +3 -0
- package/examples/react/ComplexApp/src/collections/RevenueCollection.ts +3 -0
- package/examples/react/ComplexApp/src/collections/TrafficCollection.ts +3 -0
- package/examples/react/ComplexApp/src/collections/UsersMetricCollection.ts +3 -0
- package/examples/react/ComplexApp/src/components/activity/ActivityFeed.tsx +31 -0
- package/examples/react/ComplexApp/src/components/activity/ActivityItemRow.tsx +35 -0
- package/examples/react/ComplexApp/src/components/dashboard/DashboardCard.tsx +37 -0
- package/examples/react/ComplexApp/src/components/dashboard/DashboardPage.tsx +34 -0
- package/examples/react/ComplexApp/src/components/layout/Navbar.tsx +32 -0
- package/examples/react/ComplexApp/src/components/layout/SocialFeedPanel.tsx +57 -0
- package/examples/react/ComplexApp/src/components/shared/Spinner.tsx +3 -0
- package/examples/react/ComplexApp/src/components/shared/StatusIndicator.tsx +13 -0
- package/examples/react/ComplexApp/src/components/shared/Toast.tsx +40 -0
- package/examples/react/ComplexApp/src/env.d.ts +10 -0
- package/examples/react/ComplexApp/src/events/AppEventBus.ts +7 -0
- package/examples/react/ComplexApp/src/main.tsx +10 -0
- package/examples/react/ComplexApp/src/mock-remote/MockWebSocket.ts +38 -0
- package/examples/react/ComplexApp/src/mock-remote/activity-api.ts +48 -0
- package/examples/react/ComplexApp/src/mock-remote/dashboard-generators.ts +45 -0
- package/examples/react/ComplexApp/src/mock-remote/delay.ts +18 -0
- package/examples/react/ComplexApp/src/mock-remote/social-api.ts +55 -0
- package/examples/react/ComplexApp/src/resources/ActivityResource.ts +12 -0
- package/examples/react/ComplexApp/src/resources/SocialFeedResource.ts +17 -0
- package/examples/react/ComplexApp/src/styles.css +463 -0
- package/examples/react/ComplexApp/src/types/activity.ts +8 -0
- package/examples/react/ComplexApp/src/types/dashboard.ts +5 -0
- package/examples/react/ComplexApp/src/types/social.ts +8 -0
- package/examples/react/ComplexApp/src/types/users.ts +6 -0
- package/examples/react/ComplexApp/src/viewmodels/ActivityFeedViewModel.ts +68 -0
- package/examples/react/ComplexApp/src/viewmodels/AppStateViewModel.ts +26 -0
- package/examples/react/ComplexApp/src/viewmodels/DashboardCardViewModel.ts +69 -0
- package/examples/react/ComplexApp/src/viewmodels/ErrorsCardViewModel.ts +9 -0
- package/examples/react/ComplexApp/src/viewmodels/LatencyCardViewModel.ts +9 -0
- package/examples/react/ComplexApp/src/viewmodels/OrdersCardViewModel.ts +9 -0
- package/examples/react/ComplexApp/src/viewmodels/RevenueCardViewModel.ts +9 -0
- package/examples/react/ComplexApp/src/viewmodels/SocialFeedViewModel.ts +39 -0
- package/examples/react/ComplexApp/src/viewmodels/TrafficCardViewModel.ts +9 -0
- package/examples/react/ComplexApp/src/viewmodels/UsersMetricCardViewModel.ts +9 -0
- package/examples/react/ComplexApp/tsconfig.json +22 -0
- package/examples/react/ComplexApp/vite.config.ts +18 -0
- package/examples/react/FullApp/index.html +12 -0
- package/examples/react/FullApp/src/App.tsx +28 -0
- package/examples/react/FullApp/src/collections/ConversationsCollection.ts +4 -0
- package/examples/react/FullApp/src/collections/LocationsCollection.ts +4 -0
- package/examples/react/FullApp/src/components/auth/LoginPage.tsx +80 -0
- package/examples/react/FullApp/src/components/dashboard/DashboardPage.tsx +29 -0
- package/examples/react/FullApp/src/components/dashboard/RecentActivityCard.tsx +35 -0
- package/examples/react/FullApp/src/components/dashboard/StatsCard.tsx +19 -0
- package/examples/react/FullApp/src/components/layout/AppShell.tsx +31 -0
- package/examples/react/FullApp/src/components/layout/Header.tsx +25 -0
- package/examples/react/FullApp/src/components/layout/Sidebar.tsx +29 -0
- package/examples/react/FullApp/src/components/locations/LocationFilters.tsx +60 -0
- package/examples/react/FullApp/src/components/locations/LocationForm.tsx +112 -0
- package/examples/react/FullApp/src/components/locations/LocationProfilePage.tsx +81 -0
- package/examples/react/FullApp/src/components/locations/LocationsPage.tsx +127 -0
- package/examples/react/FullApp/src/components/messaging/ConversationList.tsx +59 -0
- package/examples/react/FullApp/src/components/messaging/MessageBubble.tsx +22 -0
- package/examples/react/FullApp/src/components/messaging/MessageThread.tsx +100 -0
- package/examples/react/FullApp/src/components/messaging/MessagingPage.tsx +52 -0
- package/examples/react/FullApp/src/components/shared/ErrorBanner.tsx +3 -0
- package/examples/react/FullApp/src/components/shared/Spinner.tsx +7 -0
- package/examples/react/FullApp/src/components/shared/Toast.tsx +41 -0
- package/examples/react/FullApp/src/components/users/UserFilters.tsx +59 -0
- package/examples/react/FullApp/src/components/users/UsersPage.tsx +80 -0
- package/examples/react/FullApp/src/components/users/UsersTable.tsx +52 -0
- package/examples/react/FullApp/src/env.d.ts +10 -0
- package/examples/react/FullApp/src/events/AppEventBus.ts +7 -0
- package/examples/react/FullApp/src/main.tsx +10 -0
- package/examples/react/FullApp/src/mock/delay.ts +21 -0
- package/examples/react/FullApp/src/mock/locations.ts +76 -0
- package/examples/react/FullApp/src/mock/messages.ts +237 -0
- package/examples/react/FullApp/src/mock/users.ts +84 -0
- package/examples/react/FullApp/src/models/LocationFormModel.ts +31 -0
- package/examples/react/FullApp/src/models/LoginFormModel.ts +19 -0
- package/examples/react/FullApp/src/resources/UsersResource.ts +12 -0
- package/examples/react/FullApp/src/services/AuthService.ts +18 -0
- package/examples/react/FullApp/src/services/LocationService.ts +23 -0
- package/examples/react/FullApp/src/services/MessageService.ts +65 -0
- package/examples/react/FullApp/src/services/UserService.ts +23 -0
- package/examples/react/FullApp/src/styles.css +767 -0
- package/examples/react/FullApp/src/types/conversation.ts +7 -0
- package/examples/react/FullApp/src/types/location.ts +12 -0
- package/examples/react/FullApp/src/types/message.ts +7 -0
- package/examples/react/FullApp/src/types/user.ts +10 -0
- package/examples/react/FullApp/src/viewmodels/AuthViewModel.ts +51 -0
- package/examples/react/FullApp/src/viewmodels/ConversationsViewModel.ts +89 -0
- package/examples/react/FullApp/src/viewmodels/DashboardViewModel.ts +56 -0
- package/examples/react/FullApp/src/viewmodels/LocationProfileViewModel.ts +81 -0
- package/examples/react/FullApp/src/viewmodels/LocationsViewModel.ts +113 -0
- package/examples/react/FullApp/src/viewmodels/MessageThreadViewModel.ts +83 -0
- package/examples/react/FullApp/src/viewmodels/UsersViewModel.ts +88 -0
- package/examples/react/FullApp/tsconfig.json +22 -0
- package/examples/react/FullApp/vite.config.ts +18 -0
- package/examples/react/WorkerApp/index.html +12 -0
- package/examples/react/WorkerApp/src/App.tsx +24 -0
- package/examples/react/WorkerApp/src/channels/MessagingChannel.ts +46 -0
- package/examples/react/WorkerApp/src/channels/WorkerStatusChannel.ts +35 -0
- package/examples/react/WorkerApp/src/components/auth/LoginPage.tsx +60 -0
- package/examples/react/WorkerApp/src/components/layout/AppShell.tsx +31 -0
- package/examples/react/WorkerApp/src/components/layout/Header.tsx +23 -0
- package/examples/react/WorkerApp/src/components/layout/Sidebar.tsx +28 -0
- package/examples/react/WorkerApp/src/components/messaging/ComposeBar.tsx +33 -0
- package/examples/react/WorkerApp/src/components/messaging/ConversationList.tsx +59 -0
- package/examples/react/WorkerApp/src/components/messaging/MessageBubble.tsx +45 -0
- package/examples/react/WorkerApp/src/components/messaging/MessageThread.tsx +93 -0
- package/examples/react/WorkerApp/src/components/messaging/MessagingPage.tsx +53 -0
- package/examples/react/WorkerApp/src/components/shared/ErrorBanner.tsx +3 -0
- package/examples/react/WorkerApp/src/components/shared/PendingBanner.tsx +37 -0
- package/examples/react/WorkerApp/src/components/shared/Spinner.tsx +7 -0
- package/examples/react/WorkerApp/src/components/shared/Toast.tsx +41 -0
- package/examples/react/WorkerApp/src/components/shift/ShiftPage.tsx +98 -0
- package/examples/react/WorkerApp/src/components/shift/ShiftTimer.tsx +24 -0
- package/examples/react/WorkerApp/src/components/shift/SiteSelector.tsx +27 -0
- package/examples/react/WorkerApp/src/components/sites/SiteFilters.tsx +61 -0
- package/examples/react/WorkerApp/src/components/sites/SitesPage.tsx +102 -0
- package/examples/react/WorkerApp/src/env.d.ts +10 -0
- package/examples/react/WorkerApp/src/events/AppEventBus.ts +7 -0
- package/examples/react/WorkerApp/src/main.tsx +10 -0
- package/examples/react/WorkerApp/src/mock/MockWebSocket.ts +38 -0
- package/examples/react/WorkerApp/src/mock/delay.ts +31 -0
- package/examples/react/WorkerApp/src/mock/messages.ts +120 -0
- package/examples/react/WorkerApp/src/mock/shifts.ts +57 -0
- package/examples/react/WorkerApp/src/mock/sites.ts +14 -0
- package/examples/react/WorkerApp/src/mock/workers.ts +12 -0
- package/examples/react/WorkerApp/src/models/ComposeMessageModel.ts +17 -0
- package/examples/react/WorkerApp/src/resources/ConversationsResource.ts +10 -0
- package/examples/react/WorkerApp/src/resources/MessagesResource.ts +32 -0
- package/examples/react/WorkerApp/src/resources/ShiftResource.ts +73 -0
- package/examples/react/WorkerApp/src/resources/SitesResource.ts +11 -0
- package/examples/react/WorkerApp/src/resources/WorkersResource.ts +11 -0
- package/examples/react/WorkerApp/src/styles.css +756 -0
- package/examples/react/WorkerApp/src/types/conversation.ts +7 -0
- package/examples/react/WorkerApp/src/types/message.ts +7 -0
- package/examples/react/WorkerApp/src/types/shift.ts +13 -0
- package/examples/react/WorkerApp/src/types/site.ts +8 -0
- package/examples/react/WorkerApp/src/types/worker.ts +8 -0
- package/examples/react/WorkerApp/src/viewmodels/AuthViewModel.ts +41 -0
- package/examples/react/WorkerApp/src/viewmodels/ConversationsViewModel.ts +83 -0
- package/examples/react/WorkerApp/src/viewmodels/MessageThreadViewModel.ts +113 -0
- package/examples/react/WorkerApp/src/viewmodels/ShiftViewModel.ts +147 -0
- package/examples/react/WorkerApp/src/viewmodels/SitesViewModel.ts +82 -0
- package/examples/react/WorkerApp/tsconfig.json +22 -0
- package/examples/react/WorkerApp/vite.config.ts +18 -0
- package/package.json +4 -2
- /package/agent-config/claude-code/skills/{guide → mvc-kit}/anti-patterns.md +0 -0
- /package/agent-config/claude-code/skills/{guide → mvc-kit}/api-reference.md +0 -0
- /package/agent-config/claude-code/skills/{guide → mvc-kit}/patterns.md +0 -0
- /package/agent-config/claude-code/skills/{guide → mvc-kit}/recipes.md +0 -0
- /package/agent-config/claude-code/skills/{guide → mvc-kit}/testing.md +0 -0
- /package/agent-config/claude-code/skills/{review → mvc-kit-review}/SKILL.md +0 -0
- /package/agent-config/claude-code/skills/{review → mvc-kit-review}/checklist.md +0 -0
- /package/agent-config/claude-code/skills/{scaffold → mvc-kit-scaffold}/SKILL.md +0 -0
- /package/agent-config/claude-code/skills/{scaffold → mvc-kit-scaffold}/templates/channel.md +0 -0
- /package/agent-config/claude-code/skills/{scaffold → mvc-kit-scaffold}/templates/collection.md +0 -0
- /package/agent-config/claude-code/skills/{scaffold → mvc-kit-scaffold}/templates/controller.md +0 -0
- /package/agent-config/claude-code/skills/{scaffold → mvc-kit-scaffold}/templates/eventbus.md +0 -0
- /package/agent-config/claude-code/skills/{scaffold → mvc-kit-scaffold}/templates/model.md +0 -0
- /package/agent-config/claude-code/skills/{scaffold → mvc-kit-scaffold}/templates/page-component.md +0 -0
- /package/agent-config/claude-code/skills/{scaffold → mvc-kit-scaffold}/templates/persistent-collection.md +0 -0
- /package/agent-config/claude-code/skills/{scaffold → mvc-kit-scaffold}/templates/resource.md +0 -0
- /package/agent-config/claude-code/skills/{scaffold → mvc-kit-scaffold}/templates/service.md +0 -0
- /package/agent-config/claude-code/skills/{scaffold → mvc-kit-scaffold}/templates/viewmodel.md +0 -0
|
@@ -6,14 +6,14 @@ tools: Read, Grep, Glob, Bash
|
|
|
6
6
|
memory: project
|
|
7
7
|
color: blue
|
|
8
8
|
skills:
|
|
9
|
-
- mvc-kit
|
|
9
|
+
- mvc-kit
|
|
10
10
|
---
|
|
11
11
|
|
|
12
12
|
You are an architecture planning agent for applications built with **mvc-kit**, a TypeScript-first reactive state management library for React. You help developers plan features by deciding which classes to create, designing state shapes, and selecting sharing patterns.
|
|
13
13
|
|
|
14
14
|
## Documentation
|
|
15
15
|
|
|
16
|
-
The mvc-kit framework reference skill is preloaded into this agent's context. For deeper reference, read the supporting files in the mvc-kit skill directory (search for `.claude/skills/mvc-kit/` or `node_modules/mvc-kit/agent-config/claude-code/skills/
|
|
16
|
+
The mvc-kit framework reference skill is preloaded into this agent's context. For deeper reference, read the supporting files in the mvc-kit skill directory (search for `.claude/skills/mvc-kit/` or `node_modules/mvc-kit/agent-config/claude-code/skills/mvc-kit/`):
|
|
17
17
|
|
|
18
18
|
- `api-reference.md` — Full API reference for all classes and hooks
|
|
19
19
|
- `patterns.md` — Prescribed patterns with code examples
|
|
@@ -21,7 +21,12 @@ The mvc-kit framework reference skill is preloaded into this agent's context. Fo
|
|
|
21
21
|
- `recipes.md` — Composition recipes for real-world features
|
|
22
22
|
- `testing.md` — Testing patterns
|
|
23
23
|
|
|
24
|
-
For
|
|
24
|
+
For deep dives on any specific class or hook, search for the `.md` file by name in `node_modules/mvc-kit/src/`:
|
|
25
|
+
|
|
26
|
+
- **Core:** `ViewModel.md`, `Model.md`, `Collection.md`, `PersistentCollection.md`, `Resource.md`, `Service.md`, `EventBus.md`, `Channel.md`, `Controller.md`, `Trackable.md`, `singleton.md`
|
|
27
|
+
- **Helpers:** `Sorting.md`, `Pagination.md`, `Selection.md`, `Feed.md`, `Pending.md`, `produceDraft.md`
|
|
28
|
+
- **React hooks:** `react/use-local.md`, `react/use-instance.md`, `react/use-singleton.md`, `react/use-model.md`, `react/use-event-bus.md`, `react/use-teardown.md`
|
|
29
|
+
- **Components:** `react/components/DataTable.md`, `react/components/CardList.md`, `react/components/InfiniteScroll.md`
|
|
25
30
|
|
|
26
31
|
## Core Classes
|
|
27
32
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
name: mvc-kit
|
|
2
|
+
name: mvc-kit
|
|
3
3
|
description: "mvc-kit framework reference — class roles, architecture rules, React hooks, and decision framework. Use when working with mvc-kit imports or planning mvc-kit features."
|
|
4
4
|
user-invocable: false
|
|
5
5
|
---
|
|
@@ -97,3 +97,12 @@ For full composition recipes with code, see [recipes.md](recipes.md).
|
|
|
97
97
|
- [anti-patterns.md](anti-patterns.md) — Anti-patterns to reject with fixes
|
|
98
98
|
- [recipes.md](recipes.md) — Composition recipes for real-world features
|
|
99
99
|
- [testing.md](testing.md) — Testing patterns (teardownAll, async assertions, memoization verification)
|
|
100
|
+
|
|
101
|
+
### Per-Class Documentation (in `node_modules/mvc-kit/src/`)
|
|
102
|
+
|
|
103
|
+
For deep dives on any specific class or hook, search for the `.md` file by name:
|
|
104
|
+
|
|
105
|
+
- **Core:** `ViewModel.md`, `Model.md`, `Collection.md`, `PersistentCollection.md`, `Resource.md`, `Service.md`, `EventBus.md`, `Channel.md`, `Controller.md`, `Trackable.md`, `singleton.md`
|
|
106
|
+
- **Helpers:** `Sorting.md`, `Pagination.md`, `Selection.md`, `Feed.md`, `Pending.md`, `produceDraft.md`
|
|
107
|
+
- **React hooks:** `react/use-local.md`, `react/use-instance.md`, `react/use-singleton.md`, `react/use-model.md`, `react/use-event-bus.md`, `react/use-teardown.md`
|
|
108
|
+
- **Components:** `react/components/DataTable.md`, `react/components/CardList.md`, `react/components/InfiniteScroll.md`
|
|
@@ -1,37 +1,36 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { join, dirname } from 'node:path';
|
|
1
|
+
import { cpSync, mkdirSync, readdirSync, statSync } from 'node:fs';
|
|
2
|
+
import { join, dirname, relative } from 'node:path';
|
|
3
3
|
import { fileURLToPath } from 'node:url';
|
|
4
4
|
|
|
5
5
|
const __filename = fileURLToPath(import.meta.url);
|
|
6
6
|
const __dirname = dirname(__filename);
|
|
7
|
-
const
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
const
|
|
11
|
-
|
|
12
|
-
function
|
|
13
|
-
const
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
return AUTO_HEADER + '\n\n' + content;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
function ensureDir(dir) {
|
|
27
|
-
if (!existsSync(dir)) {
|
|
28
|
-
mkdirSync(dir, { recursive: true });
|
|
7
|
+
const SOURCE_DIR = join(__dirname, '..', 'claude-code');
|
|
8
|
+
|
|
9
|
+
const SKILL_NAMES = ['mvc-kit', 'mvc-kit-review', 'mvc-kit-scaffold'];
|
|
10
|
+
const AGENT_FILES = ['mvc-kit-architect.md'];
|
|
11
|
+
|
|
12
|
+
function listFilesRecursive(dir) {
|
|
13
|
+
const out = [];
|
|
14
|
+
for (const entry of readdirSync(dir)) {
|
|
15
|
+
const full = join(dir, entry);
|
|
16
|
+
if (statSync(full).isDirectory()) {
|
|
17
|
+
for (const nested of listFilesRecursive(full)) {
|
|
18
|
+
out.push(join(entry, nested));
|
|
19
|
+
}
|
|
20
|
+
} else {
|
|
21
|
+
out.push(entry);
|
|
22
|
+
}
|
|
29
23
|
}
|
|
24
|
+
return out;
|
|
30
25
|
}
|
|
31
26
|
|
|
32
27
|
/**
|
|
33
28
|
* Install Claude Code integration files into a project's .claude/ directory.
|
|
34
29
|
*
|
|
30
|
+
* Source directories under `agent-config/claude-code/` are copied verbatim —
|
|
31
|
+
* no frontmatter rewriting, no header injection. Source files are authored
|
|
32
|
+
* in their final installed form.
|
|
33
|
+
*
|
|
35
34
|
* Creates:
|
|
36
35
|
* .claude/skills/mvc-kit/ — Framework reference skill + supporting docs
|
|
37
36
|
* .claude/skills/mvc-kit-review/ — Code review skill + checklist
|
|
@@ -39,103 +38,33 @@ function ensureDir(dir) {
|
|
|
39
38
|
* .claude/agents/mvc-kit-architect.md — Architecture planning agent
|
|
40
39
|
*
|
|
41
40
|
* @param {string} projectRoot — Absolute path to the consuming project's root
|
|
42
|
-
* @returns {{ files: string[] }} — List of
|
|
41
|
+
* @returns {{ files: string[] }} — List of installed file paths (relative to projectRoot)
|
|
43
42
|
*/
|
|
44
43
|
export function installClaude(projectRoot) {
|
|
45
44
|
const claudeDir = join(projectRoot, '.claude');
|
|
46
|
-
const
|
|
47
|
-
const
|
|
48
|
-
const scaffoldDir = join(claudeDir, 'skills', 'mvc-kit-scaffold');
|
|
49
|
-
const templatesDir = join(scaffoldDir, 'templates');
|
|
50
|
-
const agentsDir = join(claudeDir, 'agents');
|
|
45
|
+
const skillsDest = join(claudeDir, 'skills');
|
|
46
|
+
const agentsDest = join(claudeDir, 'agents');
|
|
51
47
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
ensureDir(scaffoldDir);
|
|
55
|
-
ensureDir(templatesDir);
|
|
56
|
-
ensureDir(agentsDir);
|
|
48
|
+
mkdirSync(skillsDest, { recursive: true });
|
|
49
|
+
mkdirSync(agentsDest, { recursive: true });
|
|
57
50
|
|
|
58
51
|
const files = [];
|
|
59
52
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
const GUIDE_FRONTMATTER = `---
|
|
68
|
-
name: mvc-kit
|
|
69
|
-
description: "mvc-kit framework reference — class roles, architecture rules, React hooks, and decision framework. Loaded on-demand when working with mvc-kit code."
|
|
70
|
-
user-invocable: false
|
|
71
|
-
---`;
|
|
72
|
-
|
|
73
|
-
const skillContent = GUIDE_FRONTMATTER + '\n\n' + AUTO_HEADER + '\n\n' + guideBody + `
|
|
74
|
-
|
|
75
|
-
## Supporting Files
|
|
76
|
-
|
|
77
|
-
- [api-reference.md](api-reference.md) — Full API reference for all classes and hooks
|
|
78
|
-
- [patterns.md](patterns.md) — Prescribed patterns with code examples
|
|
79
|
-
- [anti-patterns.md](anti-patterns.md) — Anti-patterns to reject with fixes
|
|
80
|
-
- [recipes.md](recipes.md) — Composition recipes for real-world features
|
|
81
|
-
- [testing.md](testing.md) — Testing patterns (teardownAll, async assertions, memoization verification)
|
|
82
|
-
|
|
83
|
-
For detailed per-class documentation, read the \`.md\` files colocated with source in:
|
|
84
|
-
\`node_modules/mvc-kit/src/\`
|
|
85
|
-
`;
|
|
86
|
-
|
|
87
|
-
writeFileSync(join(guideDir, 'SKILL.md'), skillContent, 'utf-8');
|
|
88
|
-
files.push('.claude/skills/mvc-kit/SKILL.md');
|
|
89
|
-
|
|
90
|
-
// Copy supporting files alongside SKILL.md
|
|
91
|
-
const guideSupportFiles = ['api-reference.md', 'patterns.md', 'anti-patterns.md', 'recipes.md', 'testing.md'];
|
|
92
|
-
for (const file of guideSupportFiles) {
|
|
93
|
-
const src = join(SKILLS_DIR, 'guide', file);
|
|
94
|
-
if (existsSync(src)) {
|
|
95
|
-
copyFileSync(src, join(guideDir, file));
|
|
96
|
-
files.push(`.claude/skills/mvc-kit/${file}`);
|
|
53
|
+
for (const skill of SKILL_NAMES) {
|
|
54
|
+
const src = join(SOURCE_DIR, 'skills', skill);
|
|
55
|
+
const dest = join(skillsDest, skill);
|
|
56
|
+
cpSync(src, dest, { recursive: true, force: true });
|
|
57
|
+
for (const rel of listFilesRecursive(src)) {
|
|
58
|
+
files.push(`.claude/skills/${skill}/${rel}`);
|
|
97
59
|
}
|
|
98
60
|
}
|
|
99
61
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
copyFileSync(
|
|
107
|
-
join(SKILLS_DIR, 'review', 'checklist.md'),
|
|
108
|
-
join(reviewDir, 'checklist.md'),
|
|
109
|
-
);
|
|
110
|
-
files.push('.claude/skills/mvc-kit-review/checklist.md');
|
|
111
|
-
|
|
112
|
-
// ─── 3. Scaffold skill — code generation with templates ────────────────────
|
|
113
|
-
|
|
114
|
-
const scaffoldSkill = readFileSync(join(SKILLS_DIR, 'scaffold', 'SKILL.md'), 'utf-8');
|
|
115
|
-
writeFileSync(join(scaffoldDir, 'SKILL.md'), addHeader(scaffoldSkill), 'utf-8');
|
|
116
|
-
files.push('.claude/skills/mvc-kit-scaffold/SKILL.md');
|
|
117
|
-
|
|
118
|
-
const templatesSrc = join(SKILLS_DIR, 'scaffold', 'templates');
|
|
119
|
-
if (existsSync(templatesSrc)) {
|
|
120
|
-
for (const file of readdirSync(templatesSrc)) {
|
|
121
|
-
if (file.endsWith('.md')) {
|
|
122
|
-
copyFileSync(join(templatesSrc, file), join(templatesDir, file));
|
|
123
|
-
files.push(`.claude/skills/mvc-kit-scaffold/templates/${file}`);
|
|
124
|
-
}
|
|
125
|
-
}
|
|
62
|
+
for (const agent of AGENT_FILES) {
|
|
63
|
+
const src = join(SOURCE_DIR, 'agents', agent);
|
|
64
|
+
const dest = join(agentsDest, agent);
|
|
65
|
+
cpSync(src, dest, { force: true });
|
|
66
|
+
files.push(`.claude/agents/${agent}`);
|
|
126
67
|
}
|
|
127
68
|
|
|
128
|
-
// ─── 4. Architect agent ────────────────────────────────────────────────────
|
|
129
|
-
|
|
130
|
-
let architectAgent = readFileSync(join(AGENTS_DIR, 'mvc-kit-architect.md'), 'utf-8');
|
|
131
|
-
// The source agent preloads "mvc-kit-guide" (the source skill name).
|
|
132
|
-
// In the installed context the guide skill is named "mvc-kit", so fix the reference.
|
|
133
|
-
architectAgent = architectAgent.replace(
|
|
134
|
-
/^(\s*- )mvc-kit-guide$/m,
|
|
135
|
-
'$1mvc-kit',
|
|
136
|
-
);
|
|
137
|
-
writeFileSync(join(agentsDir, 'mvc-kit-architect.md'), addHeader(architectAgent), 'utf-8');
|
|
138
|
-
files.push('.claude/agents/mvc-kit-architect.md');
|
|
139
|
-
|
|
140
69
|
return { files };
|
|
141
70
|
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { Channel, singleton, teardownAll } from 'mvc-kit';
|
|
2
|
+
import type { ChannelStatus } from 'mvc-kit';
|
|
3
|
+
|
|
4
|
+
// Channel: Persistent connection with auto-reconnect and typed messages
|
|
5
|
+
//
|
|
6
|
+
// Extend Channel<MessageMap> and implement two abstract methods:
|
|
7
|
+
// open(signal) — establish the connection (WebSocket, SSE, etc.)
|
|
8
|
+
// close() — tear down the transport
|
|
9
|
+
//
|
|
10
|
+
// The framework handles connection status, reconnect with exponential
|
|
11
|
+
// backoff, message routing, and lifecycle management.
|
|
12
|
+
|
|
13
|
+
// --- Message type map ---
|
|
14
|
+
|
|
15
|
+
interface ChatMessages {
|
|
16
|
+
message: { userId: string; text: string };
|
|
17
|
+
typing: { userId: string };
|
|
18
|
+
presence: { online: string[] };
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// --- Channel subclass ---
|
|
22
|
+
|
|
23
|
+
class ChatChannel extends Channel<ChatMessages> {
|
|
24
|
+
// Tune reconnect behavior via static overrides
|
|
25
|
+
static override RECONNECT_BASE = 1000; // initial backoff (ms)
|
|
26
|
+
static override RECONNECT_MAX = 30000; // max backoff cap (ms)
|
|
27
|
+
static override MAX_ATTEMPTS = 5; // give up after 5 attempts
|
|
28
|
+
|
|
29
|
+
private ws: { close(): void } | null = null;
|
|
30
|
+
|
|
31
|
+
// open() is called by the framework when connect() is invoked.
|
|
32
|
+
// The signal aborts on disconnect() or dispose().
|
|
33
|
+
protected open(signal: AbortSignal): void {
|
|
34
|
+
// In production, this would be:
|
|
35
|
+
// this.ws = new WebSocket('wss://chat.example.com');
|
|
36
|
+
// this.ws.onmessage = (e) => {
|
|
37
|
+
// const { type, payload } = JSON.parse(e.data);
|
|
38
|
+
// this.receive(type, payload);
|
|
39
|
+
// };
|
|
40
|
+
|
|
41
|
+
// Simulated connection
|
|
42
|
+
console.log('Connecting...');
|
|
43
|
+
const connectTimer = setTimeout(() => {
|
|
44
|
+
if (!signal.aborted) {
|
|
45
|
+
console.log('Connected!');
|
|
46
|
+
// Simulate incoming messages
|
|
47
|
+
this.receive('presence', { online: ['alice', 'bob'] });
|
|
48
|
+
this.receive('message', { userId: 'alice', text: 'Hello!' });
|
|
49
|
+
}
|
|
50
|
+
}, 100);
|
|
51
|
+
|
|
52
|
+
this.ws = {
|
|
53
|
+
close: () => clearTimeout(connectTimer),
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
// Always clean up when the signal aborts (disconnect or dispose)
|
|
57
|
+
signal.addEventListener('abort', () => this.ws?.close());
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// close() tears down the transport. Must not throw.
|
|
61
|
+
protected close(): void {
|
|
62
|
+
this.ws?.close();
|
|
63
|
+
this.ws = null;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// --- Usage ---
|
|
68
|
+
|
|
69
|
+
const chat = singleton(ChatChannel);
|
|
70
|
+
chat.init();
|
|
71
|
+
|
|
72
|
+
// Subscribe to connection status changes
|
|
73
|
+
chat.subscribe((next: ChannelStatus, prev: ChannelStatus) => {
|
|
74
|
+
console.log(`Status: connected=${next.connected}, reconnecting=${next.reconnecting}`);
|
|
75
|
+
if (next.error) console.log(`Error: ${next.error}`);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
// Subscribe to typed messages
|
|
79
|
+
const unsubMessage = chat.on('message', ({ userId, text }) => {
|
|
80
|
+
console.log(`${userId}: ${text}`);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
// One-time subscription — auto-unsubscribes after first event
|
|
84
|
+
chat.once('presence', ({ online }) => {
|
|
85
|
+
console.log('Initial presence:', online);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
// Initiate connection
|
|
89
|
+
chat.connect();
|
|
90
|
+
|
|
91
|
+
// Read current status at any time
|
|
92
|
+
console.log('Connected:', chat.state.connected);
|
|
93
|
+
console.log('Reconnecting:', chat.state.reconnecting);
|
|
94
|
+
console.log('Attempt:', chat.state.attempt);
|
|
95
|
+
|
|
96
|
+
// Manually disconnect (cancels pending reconnect, resets status)
|
|
97
|
+
// chat.disconnect();
|
|
98
|
+
|
|
99
|
+
// Reconnect after disconnect
|
|
100
|
+
// chat.connect();
|
|
101
|
+
|
|
102
|
+
// Unsubscribe from a message type
|
|
103
|
+
unsubMessage();
|
|
104
|
+
|
|
105
|
+
// Cleanup — cancels timers, aborts signals, calls close()
|
|
106
|
+
setTimeout(() => {
|
|
107
|
+
chat.dispose();
|
|
108
|
+
teardownAll();
|
|
109
|
+
}, 500);
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { Collection } from 'mvc-kit';
|
|
2
|
+
|
|
3
|
+
// Collection: Reactive typed array with CRUD and query methods
|
|
4
|
+
|
|
5
|
+
interface Todo {
|
|
6
|
+
id: string;
|
|
7
|
+
text: string;
|
|
8
|
+
done: boolean;
|
|
9
|
+
priority: 'low' | 'medium' | 'high';
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
// --- Basic usage ---
|
|
13
|
+
|
|
14
|
+
const todos = new Collection<Todo>();
|
|
15
|
+
|
|
16
|
+
// Subscribe to changes
|
|
17
|
+
todos.subscribe((items, prev) => {
|
|
18
|
+
console.log(`Collection changed: ${prev.length} → ${items.length} items`);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
// CRUD operations (trigger notifications)
|
|
22
|
+
|
|
23
|
+
// Add items
|
|
24
|
+
todos.add(
|
|
25
|
+
{ id: '1', text: 'Learn mvc-kit', done: false, priority: 'high' },
|
|
26
|
+
{ id: '2', text: 'Build an app', done: false, priority: 'medium' },
|
|
27
|
+
{ id: '3', text: 'Write tests', done: true, priority: 'low' }
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
console.log('Length:', todos.length); // 3
|
|
31
|
+
console.log('Items:', todos.items);
|
|
32
|
+
|
|
33
|
+
// Update an item
|
|
34
|
+
todos.update('1', { done: true });
|
|
35
|
+
console.log('After update:', todos.get('1')?.done); // true
|
|
36
|
+
|
|
37
|
+
// Remove an item
|
|
38
|
+
todos.remove('3');
|
|
39
|
+
console.log('After remove length:', todos.length); // 2
|
|
40
|
+
|
|
41
|
+
// Query operations (pure, no notifications)
|
|
42
|
+
|
|
43
|
+
// Get by id (O(1) lookup)
|
|
44
|
+
const todo = todos.get('1');
|
|
45
|
+
console.log('Get by id:', todo?.text);
|
|
46
|
+
|
|
47
|
+
// Check existence
|
|
48
|
+
console.log('Has id 1:', todos.has('1')); // true
|
|
49
|
+
console.log('Has id 99:', todos.has('99')); // false
|
|
50
|
+
|
|
51
|
+
// Find first match
|
|
52
|
+
const firstIncomplete = todos.find(t => !t.done);
|
|
53
|
+
console.log('First incomplete:', firstIncomplete?.text);
|
|
54
|
+
|
|
55
|
+
// Filter
|
|
56
|
+
const highPriority = todos.filter(t => t.priority === 'high');
|
|
57
|
+
console.log('High priority count:', highPriority.length);
|
|
58
|
+
|
|
59
|
+
// Sort (returns new array, doesn't mutate)
|
|
60
|
+
const sorted = todos.sorted((a, b) => a.text.localeCompare(b.text));
|
|
61
|
+
console.log('Sorted:', sorted.map(t => t.text));
|
|
62
|
+
|
|
63
|
+
// Map
|
|
64
|
+
const texts = todos.map(t => t.text);
|
|
65
|
+
console.log('Texts:', texts);
|
|
66
|
+
|
|
67
|
+
// Reset - replace all items
|
|
68
|
+
todos.reset([
|
|
69
|
+
{ id: 'a', text: 'New todo', done: false, priority: 'medium' }
|
|
70
|
+
]);
|
|
71
|
+
console.log('After reset length:', todos.length); // 1
|
|
72
|
+
|
|
73
|
+
// Clear all items
|
|
74
|
+
todos.clear();
|
|
75
|
+
console.log('After clear length:', todos.length); // 0
|
|
76
|
+
|
|
77
|
+
// --- Upsert: add-or-replace by ID ---
|
|
78
|
+
|
|
79
|
+
todos.reset([
|
|
80
|
+
{ id: '1', text: 'Learn mvc-kit', done: false, priority: 'high' },
|
|
81
|
+
{ id: '2', text: 'Build an app', done: false, priority: 'medium' },
|
|
82
|
+
]);
|
|
83
|
+
|
|
84
|
+
// Upsert replaces existing items in-place and appends new ones
|
|
85
|
+
todos.upsert(
|
|
86
|
+
{ id: '2', text: 'Build an app', done: true, priority: 'medium' }, // replaces in position
|
|
87
|
+
{ id: '3', text: 'Ship it', done: false, priority: 'high' }, // appended
|
|
88
|
+
);
|
|
89
|
+
console.log('After upsert length:', todos.length); // 3
|
|
90
|
+
console.log('Item 2 done:', todos.get('2')?.done); // true (replaced)
|
|
91
|
+
console.log('Item 3 text:', todos.get('3')?.text); // 'Ship it' (new)
|
|
92
|
+
|
|
93
|
+
// --- Optimistic updates ---
|
|
94
|
+
|
|
95
|
+
// Reset with some data for the optimistic demo
|
|
96
|
+
todos.reset([
|
|
97
|
+
{ id: '1', text: 'Learn mvc-kit', done: false, priority: 'high' },
|
|
98
|
+
{ id: '2', text: 'Build an app', done: false, priority: 'medium' },
|
|
99
|
+
]);
|
|
100
|
+
|
|
101
|
+
// Snapshot current state, apply mutations, get a rollback function
|
|
102
|
+
const rollback = todos.optimistic(() => {
|
|
103
|
+
todos.update('1', { done: true });
|
|
104
|
+
todos.remove('2');
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
console.log('After optimistic:', todos.length); // 1
|
|
108
|
+
console.log('Item 1 done:', todos.get('1')?.done); // true
|
|
109
|
+
|
|
110
|
+
// If the server call fails, rollback restores pre-optimistic state
|
|
111
|
+
rollback();
|
|
112
|
+
|
|
113
|
+
console.log('After rollback:', todos.length); // 2
|
|
114
|
+
console.log('Item 1 done:', todos.get('1')?.done); // false
|
|
115
|
+
console.log('Item 2 exists:', todos.has('2')); // true
|
|
116
|
+
|
|
117
|
+
// Cleanup
|
|
118
|
+
todos.dispose();
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { Controller, ViewModel, Collection, singleton, teardownAll } from 'mvc-kit';
|
|
2
|
+
|
|
3
|
+
// Controller: Stateless multi-ViewModel orchestrator
|
|
4
|
+
//
|
|
5
|
+
// Controllers coordinate between ViewModels, Models, and Services when
|
|
6
|
+
// a single ViewModel can't own the workflow. This is rare — most
|
|
7
|
+
// orchestration fits in a single ViewModel.
|
|
8
|
+
//
|
|
9
|
+
// Use Controller only for pure cross-cutting coordination with no state
|
|
10
|
+
// of its own: multi-step checkout, drag-and-drop between lists, etc.
|
|
11
|
+
//
|
|
12
|
+
// What Controller provides:
|
|
13
|
+
// - init() / dispose() lifecycle
|
|
14
|
+
// - subscribeTo() / listenTo() with auto-cleanup
|
|
15
|
+
// - disposeSignal for cancelling async operations
|
|
16
|
+
// - addCleanup() for custom teardown
|
|
17
|
+
//
|
|
18
|
+
// What Controller does NOT provide:
|
|
19
|
+
// - State (no set())
|
|
20
|
+
// - Computed getters
|
|
21
|
+
// - Async tracking
|
|
22
|
+
// - Events (no emit())
|
|
23
|
+
|
|
24
|
+
// --- Supporting types ---
|
|
25
|
+
|
|
26
|
+
interface Task {
|
|
27
|
+
id: string;
|
|
28
|
+
title: string;
|
|
29
|
+
status: 'todo' | 'done';
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
interface TaskListState {
|
|
33
|
+
items: Task[];
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// --- Two ViewModels that the Controller coordinates ---
|
|
37
|
+
|
|
38
|
+
class TodoListViewModel extends ViewModel<TaskListState> {
|
|
39
|
+
removeItem(id: string) {
|
|
40
|
+
this.set({ items: this.state.items.filter(t => t.id !== id) });
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
addItem(task: Task) {
|
|
44
|
+
this.set({ items: [...this.state.items, task] });
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
class DoneListViewModel extends ViewModel<TaskListState> {
|
|
49
|
+
addItem(task: Task) {
|
|
50
|
+
this.set({ items: [...this.state.items, { ...task, status: 'done' as const }] });
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// --- Controller definition ---
|
|
55
|
+
|
|
56
|
+
class TaskTransferController extends Controller {
|
|
57
|
+
constructor(
|
|
58
|
+
private todoVM: TodoListViewModel,
|
|
59
|
+
private doneVM: DoneListViewModel,
|
|
60
|
+
) {
|
|
61
|
+
super();
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
protected onInit() {
|
|
65
|
+
// subscribeTo auto-cleans up on dispose
|
|
66
|
+
this.subscribeTo(this.todoVM, (state) => {
|
|
67
|
+
console.log(`Todo list: ${state.items.length} items`);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
this.subscribeTo(this.doneVM, (state) => {
|
|
71
|
+
console.log(`Done list: ${state.items.length} items`);
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Pure coordination — moves an item from todo to done
|
|
76
|
+
completeTask(taskId: string) {
|
|
77
|
+
const task = this.todoVM.state.items.find(t => t.id === taskId);
|
|
78
|
+
if (!task) return;
|
|
79
|
+
this.todoVM.removeItem(taskId);
|
|
80
|
+
this.doneVM.addItem(task);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
protected onDispose() {
|
|
84
|
+
console.log('TaskTransferController disposed');
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// --- Usage ---
|
|
89
|
+
|
|
90
|
+
const todoVM = new TodoListViewModel({
|
|
91
|
+
items: [
|
|
92
|
+
{ id: '1', title: 'Learn mvc-kit', status: 'todo' },
|
|
93
|
+
{ id: '2', title: 'Build an app', status: 'todo' },
|
|
94
|
+
{ id: '3', title: 'Write tests', status: 'todo' },
|
|
95
|
+
],
|
|
96
|
+
});
|
|
97
|
+
todoVM.init();
|
|
98
|
+
|
|
99
|
+
const doneVM = new DoneListViewModel({ items: [] });
|
|
100
|
+
doneVM.init();
|
|
101
|
+
|
|
102
|
+
const controller = new TaskTransferController(todoVM, doneVM);
|
|
103
|
+
controller.init();
|
|
104
|
+
|
|
105
|
+
console.log('Todo:', todoVM.state.items.length); // 3
|
|
106
|
+
console.log('Done:', doneVM.state.items.length); // 0
|
|
107
|
+
|
|
108
|
+
// Controller coordinates the move
|
|
109
|
+
controller.completeTask('1');
|
|
110
|
+
|
|
111
|
+
console.log('Todo:', todoVM.state.items.length); // 2
|
|
112
|
+
console.log('Done:', doneVM.state.items.length); // 1
|
|
113
|
+
console.log('Done item:', doneVM.state.items[0]?.title); // 'Learn mvc-kit'
|
|
114
|
+
|
|
115
|
+
// Cleanup
|
|
116
|
+
controller.dispose();
|
|
117
|
+
todoVM.dispose();
|
|
118
|
+
doneVM.dispose();
|