create-janux 0.2.0 → 0.2.1

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.
@@ -1,38 +0,0 @@
1
- import { describe, expect, it } from 'bun:test';
2
- import { createInstance } from 'janux';
3
- import { TaskBoard } from './TaskBoard';
4
-
5
- describe('TaskBoard', () => {
6
- it('adds, toggles and counts tasks', async () => {
7
- const board = createInstance(TaskBoard);
8
-
9
- await board.intents.add!({ title: 'Ship it' });
10
- await board.intents.add!({ title: 'Write tests' });
11
- const [first] = board.snapshot().tasks as any[];
12
-
13
- await board.intents.toggle!({ id: first.id });
14
- expect((board.snapshot().tasks as any[])[0].done).toBe(true);
15
- expect(board.derived.remaining).toBe(1);
16
- });
17
-
18
- it('rejects empty titles', () => {
19
- const board = createInstance(TaskBoard);
20
-
21
- expect(board.intents.add!({ title: '' })).rejects.toThrow(/below min/);
22
- });
23
-
24
- it('clearDone is a proposal for agents — nothing happens until approval', async () => {
25
- let proposal: any;
26
- const board = createInstance(TaskBoard, { onProposal: (p) => (proposal = p) });
27
-
28
- await board.intents.add!({ title: 'done thing' });
29
- await board.intents.toggle!({ id: (board.snapshot().tasks as any[])[0].id });
30
-
31
- const result: any = await board.intents.clearDone!({}, { origin: 'agent' });
32
-
33
- expect(result.status).toBe('proposal');
34
- expect(board.snapshot().tasks).toHaveLength(1);
35
- await proposal.execute();
36
- expect(board.snapshot().tasks).toHaveLength(0);
37
- });
38
- });
@@ -1,125 +0,0 @@
1
- import { component, intent, effect, schema, str, bool, int, enums, list } from 'janux';
2
- import { saveTasks } from '../server/tasks.api';
3
- import { theme } from '../stores';
4
-
5
- const FILTERS = ['all', 'active', 'done'] as const;
6
-
7
- function taskId(): string {
8
- return `t_${Math.random().toString(36).slice(2, 9)}`;
9
- }
10
-
11
- function visibleTasks(tasks: any[], filter: string): any[] {
12
- if (filter === 'active') return tasks.filter((task) => !task.done);
13
- if (filter === 'done') return tasks.filter((task) => task.done);
14
-
15
- return tasks;
16
- }
17
-
18
- export const TaskBoard = component({
19
- name: 'tasks',
20
- description: 'A task board. Agents can add, toggle, filter and (with approval) clear done tasks.',
21
-
22
- state: schema({
23
- tasks: list({ id: str(), title: str(), done: bool() }),
24
- filter: enums([...FILTERS]),
25
- }),
26
-
27
- derived: {
28
- remaining: (s: any) => s.tasks.filter((task: any) => !task.done).length,
29
- },
30
-
31
- effects: {
32
- persist: effect({
33
- description: 'Saves tasks to the server after changes settle',
34
- when: (s: any) => s.tasks,
35
- debounce: '400ms',
36
- run: ({ state }: any) => saveTasks({ tasks: state.tasks }).then(() => {}),
37
- }),
38
- },
39
-
40
- emits: { 'tasks.cleared': schema({ count: int() }) },
41
-
42
- use: { theme },
43
-
44
- intents: {
45
- add: intent({
46
- description: 'Add a task by title',
47
- input: schema({ title: str().min(1) }),
48
- run: ({ state, input }: any) => state.tasks.push({ id: taskId(), title: input.title, done: false }),
49
- }),
50
- toggle: intent({
51
- description: 'Toggle a task done/undone by id',
52
- input: schema({ id: str() }),
53
- run: ({ state, input }: any) => {
54
- const task = state.tasks.find((candidate: any) => candidate.id === input.id);
55
-
56
- if (task) task.done = !task.done;
57
- },
58
- }),
59
- remove: intent({
60
- description: 'Delete a task by id',
61
- input: schema({ id: str() }),
62
- run: ({ state, input }: any) => {
63
- state.tasks = state.tasks.filter((task: any) => task.id !== input.id);
64
- },
65
- }),
66
- setFilter: intent({
67
- description: 'Show all, active or done tasks',
68
- input: schema({ filter: enums([...FILTERS]) }),
69
- run: ({ state, input }: any) => (state.filter = input.filter),
70
- }),
71
- clearDone: intent({
72
- description: 'Remove every completed task. Destructive — needs approval.',
73
- guard: 'confirm',
74
- run: ({ state, emit }: any) => {
75
- const count = state.tasks.filter((task: any) => task.done).length;
76
-
77
- state.tasks = state.tasks.filter((task: any) => !task.done);
78
- emit('tasks.cleared', { count });
79
- },
80
- }),
81
- },
82
-
83
- view: ({ state, derived, intents }: any) => (
84
- <section class="board">
85
- <header>
86
- <h2>Tasks</h2>
87
- <span class="count">{derived.remaining} left</span>
88
- </header>
89
- <form intent={intents.add}>
90
- <input name="title" placeholder="What needs doing?" autocomplete="off" />
91
- <button type="submit">Add</button>
92
- </form>
93
- <nav class="filters">
94
- {FILTERS.map((filter) => (
95
- <button
96
- key={filter}
97
- class={state.filter === filter ? 'on' : undefined}
98
- on={intents.setFilter}
99
- data-input={JSON.stringify({ filter })}
100
- >
101
- {filter}
102
- </button>
103
- ))}
104
- </nav>
105
- <ul class="list">
106
- {visibleTasks(state.tasks, state.filter).map((task: any) => (
107
- <li key={task.id} class={task.done ? 'done' : undefined}>
108
- <button class="check" on={intents.toggle} data-input={JSON.stringify({ id: task.id })}>
109
- {task.done ? '✓' : ''}
110
- </button>
111
- <span>{task.title}</span>
112
- <button class="x" on={intents.remove} data-input={JSON.stringify({ id: task.id })}>
113
-
114
- </button>
115
- </li>
116
- ))}
117
- </ul>
118
- <footer>
119
- <button class="clear" on={intents.clearDone}>
120
- Clear done
121
- </button>
122
- </footer>
123
- </section>
124
- ),
125
- });
@@ -1,23 +0,0 @@
1
- import { component, intent } from 'janux';
2
- import { theme } from '../stores';
3
-
4
- /** Second island sharing the theme store — cross-island state, no props. */
5
- export const ThemeToggle = component({
6
- name: 'theme-toggle',
7
- description: 'Switches the shared theme between dark and light.',
8
-
9
- use: { theme },
10
-
11
- intents: {
12
- toggle: intent({
13
- description: 'Toggle dark/light mode',
14
- run: ({ use }: any) => use.theme.intents.toggle({}),
15
- }),
16
- },
17
-
18
- view: ({ use, intents }: any) => (
19
- <button class="theme-toggle" on={intents.toggle}>
20
- {use.theme.state.mode === 'dark' ? '☀️ Light' : '🌙 Dark'}
21
- </button>
22
- ),
23
- });
@@ -1,31 +0,0 @@
1
- import { api } from '@janux/server';
2
- import { schema, str, bool, int, list } from 'janux';
3
-
4
- const TASK_SHAPE = { id: str(), title: str(), done: bool() };
5
- const saved = new Map<string, unknown[]>();
6
-
7
- export const loadTasks = api({
8
- description: 'Load the saved task list',
9
- output: schema({ tasks: list(TASK_SHAPE) }),
10
- run: ({ ctx }) => ({ tasks: (saved.get(String(ctx.userId ?? 'anon')) as any[]) ?? [] }),
11
- });
12
-
13
- export const saveTasks = api({
14
- description: 'Persist the task list',
15
- input: schema({ tasks: list(TASK_SHAPE) }),
16
- run: ({ input, ctx }) => {
17
- saved.set(String(ctx.userId ?? 'anon'), input.tasks);
18
-
19
- return { saved: input.tasks.length };
20
- },
21
- });
22
-
23
- export const taskStats = api({
24
- description: 'Aggregate stats over the saved tasks',
25
- output: schema({ total: int(), done: int() }),
26
- run: ({ ctx }) => {
27
- const tasks = (saved.get(String(ctx.userId ?? 'anon')) as any[]) ?? [];
28
-
29
- return { total: tasks.length, done: tasks.filter((task) => task.done).length };
30
- },
31
- });
@@ -1,17 +0,0 @@
1
- import { store, intent, schema, enums } from 'janux';
2
-
3
- /** Shared theme, projected to agents as store://theme. */
4
- export const theme = store({
5
- name: 'theme',
6
- description: 'UI theme shared by every island.',
7
- state: schema({ mode: enums(['dark', 'light']).default('dark') }),
8
- intents: {
9
- toggle: intent({
10
- description: 'Switch between dark and light mode',
11
- run: ({ state }) => {
12
- state.mode = state.mode === 'dark' ? 'light' : 'dark';
13
- if (typeof document !== 'undefined') document.documentElement.dataset.theme = state.mode;
14
- },
15
- }),
16
- },
17
- });