@saadnvd1/agent-os 0.3.603 → 0.3.605

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.
Files changed (36) hide show
  1. package/app/page.tsx +3 -8
  2. package/components/Bookmarks/ChatBookmarksButton.tsx +4 -0
  3. package/components/Bus/MessagesPanel.tsx +155 -0
  4. package/components/Bus/index.ts +1 -2
  5. package/components/Chat/ChatPanel.tsx +9 -1
  6. package/components/CommandPalette/useAppCommands.ts +12 -4
  7. package/components/Features/FeaturesSection.tsx +2 -2
  8. package/components/Inbox/InboxButton.tsx +39 -0
  9. package/components/Inbox/InboxDialog.tsx +139 -0
  10. package/components/Inbox/NeedsYouList.tsx +120 -0
  11. package/components/Inbox/index.ts +2 -0
  12. package/components/LumifyHub/index.ts +0 -1
  13. package/components/Pane/DesktopTabBar.tsx +2 -0
  14. package/components/Pane/PaneMenu.test.ts +118 -0
  15. package/components/Pane/PaneMenu.tsx +37 -0
  16. package/components/SessionList/SidebarHeader.tsx +7 -4
  17. package/components/Tasks/TaskRow.tsx +3 -3
  18. package/components/Tasks/TasksPanel.tsx +63 -0
  19. package/components/Tasks/index.ts +1 -1
  20. package/components/views/DesktopAppActions.tsx +10 -63
  21. package/components/views/DesktopView.tsx +1 -11
  22. package/hooks/useInbox.ts +69 -0
  23. package/lib/inbox/counts.ts +140 -0
  24. package/package.json +1 -1
  25. package/scripts/check-bundle.mjs +25 -10
  26. package/scripts/check-bundle.test.ts +2 -2
  27. package/stores/inboxUi.ts +60 -0
  28. package/stores/needsYouFocus.ts +11 -3
  29. package/components/Bus/MessagesButton.tsx +0 -25
  30. package/components/Bus/MessagesDialog.tsx +0 -176
  31. package/components/LumifyHub/Docs/DocsButton.tsx +0 -31
  32. package/components/NotificationsMenu.tsx +0 -86
  33. package/components/Tasks/TasksButton.tsx +0 -28
  34. package/components/Tasks/TasksDialog.tsx +0 -75
  35. package/stores/busUi.ts +0 -12
  36. package/stores/tasksUi.ts +0 -14
@@ -1,7 +1,10 @@
1
- // The app's first-load JavaScript, held under a budget: a heavy library
2
- // imported eagerly (a syntax highlighter's every grammar, a diagram renderer)
3
- // fails here instead of shipping to the phone. Run after `next build`.
4
- import { readFileSync } from "fs";
1
+ // The app's first-load JavaScript, watched against a budget. Going over it
2
+ // warns rather than fails: growth is sometimes worth it, and the point is to
3
+ // notice a big jump (a syntax highlighter's every grammar, a diagram renderer
4
+ // imported eagerly) before it ships to the phone. On CI the warning becomes an
5
+ // annotation on the PR. A missing or unreadable size still fails, so a broken
6
+ // check can't pass quietly. Run after `next build`.
7
+ import { appendFileSync, readFileSync } from "fs";
5
8
 
6
9
  // Bytes, uncompressed. 1.04 MB when this was set (2026-10-07), down from 2.07.
7
10
  const BUDGETS = { "/": 1_200_000 };
@@ -9,7 +12,20 @@ const BUDGETS = { "/": 1_200_000 };
9
12
  const statsPath =
10
13
  process.argv[2] ?? ".next/diagnostics/route-bundle-stats.json";
11
14
  const stats = JSON.parse(readFileSync(statsPath, "utf-8"));
15
+ const kb = (n) => `${Math.round(n / 1024)} KB`;
12
16
  let failed = false;
17
+
18
+ function warn(text) {
19
+ console.warn(`bundle: ${text}`);
20
+ if (process.env.GITHUB_ACTIONS)
21
+ console.log(`::warning title=Bundle size::${text}`);
22
+ if (process.env.GITHUB_STEP_SUMMARY)
23
+ appendFileSync(
24
+ process.env.GITHUB_STEP_SUMMARY,
25
+ `> [!WARNING]\n> ${text}\n`
26
+ );
27
+ }
28
+
13
29
  for (const [route, budget] of Object.entries(BUDGETS)) {
14
30
  const entry = stats.find((r) => r.route === route);
15
31
  if (!entry) {
@@ -18,18 +34,17 @@ for (const [route, budget] of Object.entries(BUDGETS)) {
18
34
  continue;
19
35
  }
20
36
  const bytes = entry.firstLoadUncompressedJsBytes;
21
- const kb = (n) => `${Math.round(n / 1024)} KB`;
22
37
  if (typeof bytes !== "number" || !Number.isFinite(bytes)) {
23
38
  // A Next.js upgrade that renames the field must not pass by default.
24
39
  console.error(`bundle: no firstLoadUncompressedJsBytes for ${route}`);
25
40
  failed = true;
26
41
  } else if (bytes > budget) {
27
- console.error(
28
- `bundle: ${route} loads ${kb(bytes)} of JS first, over its ${kb(budget)} budget.\n` +
29
- " Load the heavy part with next/dynamic or import(); see what's in it with\n" +
30
- " `npx next experimental-analyze`."
42
+ const over = Math.round(((bytes - budget) / budget) * 100);
43
+ warn(
44
+ `${route} loads ${kb(bytes)} of JS first, ${over}% over its ${kb(budget)} budget. ` +
45
+ "If that's not worth it, load the heavy part with next/dynamic or import(); " +
46
+ "`npx next experimental-analyze` shows what's in it."
31
47
  );
32
- failed = true;
33
48
  } else {
34
49
  console.log(`bundle: ${route} ${kb(bytes)} of ${kb(budget)}`);
35
50
  }
@@ -21,9 +21,9 @@ describe("the first-load JS budget", () => {
21
21
  expect(r.code).toBe(0);
22
22
  });
23
23
 
24
- it("fails a build over budget", () => {
24
+ it("warns, without failing, on a build over budget", () => {
25
25
  const r = run([{ route: "/", firstLoadUncompressedJsBytes: 2_000_000 }]);
26
- expect([r.code, r.out]).toEqual([1, expect.stringContaining("over its")]);
26
+ expect([r.code, r.out]).toEqual([0, expect.stringContaining("% over its")]);
27
27
  });
28
28
 
29
29
  it("fails closed when the size is missing or not a number", () => {
@@ -0,0 +1,60 @@
1
+ import { proxy } from "valtio";
2
+ import { INBOX_TABS, type InboxTab } from "@/lib/inbox/counts";
3
+
4
+ const TAB_KEY = "agentos:inbox-tab";
5
+ const SEEN_KEY = "agentos:inbox-messages-seen";
6
+
7
+ const read = (key: string) => {
8
+ try {
9
+ return typeof window === "undefined" ? null : localStorage.getItem(key);
10
+ } catch {
11
+ return null;
12
+ }
13
+ };
14
+ const write = (key: string, value: string) => {
15
+ try {
16
+ localStorage.setItem(key, value);
17
+ } catch {
18
+ // Private mode: the choice just isn't kept.
19
+ }
20
+ };
21
+
22
+ export const lastInboxTab = (): InboxTab | null => {
23
+ const saved = read(TAB_KEY);
24
+ return INBOX_TABS.find((t) => t === saved) ?? null;
25
+ };
26
+
27
+ // The Inbox panel, shared by the bar, the sidebar menu and the palette.
28
+ // `request` is an explicit tab to open on; without one the panel picks.
29
+ export const inboxUi = proxy<{
30
+ open: boolean;
31
+ request: InboxTab | null;
32
+ // The newest message he has looked at; null until first seen.
33
+ seenMessageId: number | null;
34
+ }>({
35
+ open: false,
36
+ request: null,
37
+ seenMessageId: null,
38
+ });
39
+
40
+ export const inboxUiActions = {
41
+ open: (tab?: InboxTab) => {
42
+ inboxUi.request = tab ?? null;
43
+ inboxUi.open = true;
44
+ },
45
+ setOpen: (open: boolean) => {
46
+ inboxUi.open = open;
47
+ },
48
+ rememberTab: (tab: InboxTab) => write(TAB_KEY, tab),
49
+ loadSeen: () => {
50
+ if (inboxUi.seenMessageId !== null) return;
51
+ const saved = read(SEEN_KEY);
52
+ if (saved !== null && Number.isFinite(Number(saved)))
53
+ inboxUi.seenMessageId = Number(saved);
54
+ },
55
+ markSeen: (id: number) => {
56
+ if (inboxUi.seenMessageId !== null && id <= inboxUi.seenMessageId) return;
57
+ inboxUi.seenMessageId = id;
58
+ write(SEEN_KEY, String(id));
59
+ },
60
+ };
@@ -1,13 +1,21 @@
1
1
  import { proxy } from "valtio";
2
2
 
3
- // "Show this ask", raised by the Needs-you strip and handled by the asks list
4
- // above the chat, which opens itself and scrolls the card into view.
3
+ // "Show this ask", raised by the Needs-you strip and the Inbox and handled
4
+ // by the asks list above the chat, which opens itself and scrolls the card
5
+ // into view. "Reply to this question" is handled by the orchestrator's chat.
5
6
  export const needsYouFocus = proxy<{
6
7
  ask: { id: number; at: number } | null;
7
- }>({ ask: null });
8
+ reply: { workspaceId: string; id: number; text: string; at: number } | null;
9
+ }>({ ask: null, reply: null });
8
10
 
9
11
  export const needsYouFocusActions = {
10
12
  ask: (id: number) => {
11
13
  needsYouFocus.ask = { id, at: Date.now() };
12
14
  },
15
+ reply: (workspaceId: string, id: number, text: string) => {
16
+ needsYouFocus.reply = { workspaceId, id, text, at: Date.now() };
17
+ },
18
+ clearReply: () => {
19
+ needsYouFocus.reply = null;
20
+ },
13
21
  };
@@ -1,25 +0,0 @@
1
- "use client";
2
-
3
- import { MessagesSquare } from "lucide-react";
4
- import { Button } from "@/components/ui/button";
5
- import { busUiActions } from "@/stores/busUi";
6
-
7
- export function MessagesButton({
8
- labelClassName,
9
- }: {
10
- labelClassName?: string;
11
- }) {
12
- return (
13
- <Button
14
- size="sm"
15
- variant="ghost"
16
- aria-label="Messages"
17
- title="Messages"
18
- className="h-7 px-2"
19
- onClick={busUiActions.open}
20
- >
21
- <MessagesSquare className="h-4 w-4" />
22
- <span className={labelClassName}>Messages</span>
23
- </Button>
24
- );
25
- }
@@ -1,176 +0,0 @@
1
- "use client";
2
-
3
- import { useMemo, useState } from "react";
4
- import { useSnapshot } from "valtio";
5
- import {
6
- Dialog,
7
- DialogContent,
8
- DialogDescription,
9
- DialogHeader,
10
- DialogTitle,
11
- } from "@/components/ui/dialog";
12
- import { Button } from "@/components/ui/button";
13
- import { Textarea } from "@/components/ui/textarea";
14
- import {
15
- Select,
16
- SelectContent,
17
- SelectItem,
18
- SelectTrigger,
19
- SelectValue,
20
- } from "@/components/ui/select";
21
- import { useBusMessages, useBusPeers, useSendMessage } from "@/data/bus";
22
- import { compactTimeAgo, fromSqliteTime } from "@/lib/session-meta";
23
- import { busUi, busUiActions } from "@/stores/busUi";
24
- import { cn } from "@/lib/utils";
25
-
26
- const ALL = "all";
27
-
28
- export function MessagesDialog() {
29
- const { open } = useSnapshot(busUi);
30
- const { data: messages = [], isPending } = useBusMessages(open);
31
- const { data: peers = [] } = useBusPeers(open);
32
- const send = useSendMessage();
33
- const [filter, setFilter] = useState(ALL);
34
- const [to, setTo] = useState("");
35
- const [body, setBody] = useState("");
36
-
37
- const label = (p: { name: string; projectName: string | null }) =>
38
- p.projectName ? `${p.projectName} / ${p.name}` : p.name;
39
- const shown = useMemo(
40
- () =>
41
- filter === ALL
42
- ? messages
43
- : messages.filter((m) => m.fromId === filter || m.toId === filter),
44
- [messages, filter]
45
- );
46
- const recipient = to || (filter !== ALL ? filter : "");
47
-
48
- return (
49
- <Dialog
50
- open={open}
51
- onOpenChange={(o) => {
52
- // A delivery result belongs to that send only.
53
- if (!o) send.reset();
54
- busUiActions.setOpen(o);
55
- }}
56
- >
57
- <DialogContent className="flex max-h-[85vh] max-w-2xl flex-col">
58
- <DialogHeader>
59
- <DialogTitle>Messages</DialogTitle>
60
- <DialogDescription>
61
- What your agent sessions are saying to each other. Agents use the{" "}
62
- <code className="font-mono text-xs">aos</code> command.
63
- </DialogDescription>
64
- </DialogHeader>
65
-
66
- <Select
67
- value={filter}
68
- onValueChange={(v) => {
69
- send.reset();
70
- setFilter(v);
71
- }}
72
- >
73
- <SelectTrigger
74
- aria-label="Filter by session"
75
- className="w-full sm:w-72"
76
- >
77
- <SelectValue />
78
- </SelectTrigger>
79
- <SelectContent>
80
- <SelectItem value={ALL}>All sessions</SelectItem>
81
- {peers.map((p) => (
82
- <SelectItem key={p.id} value={p.id}>
83
- {label(p)}
84
- </SelectItem>
85
- ))}
86
- </SelectContent>
87
- </Select>
88
-
89
- <div className="min-h-40 flex-1 space-y-3 overflow-y-auto pr-1">
90
- {isPending && (
91
- <div className="bg-muted/40 h-16 animate-pulse rounded-xl" />
92
- )}
93
- {!isPending && shown.length === 0 && (
94
- <p className="text-muted-foreground py-8 text-center text-sm">
95
- No messages yet.
96
- </p>
97
- )}
98
- {shown.map((m) => (
99
- <div key={m.id} className="space-y-1">
100
- <div className="flex items-baseline gap-2 text-xs">
101
- <span
102
- className={cn("font-medium", !m.fromId && "text-primary")}
103
- >
104
- {m.fromName}
105
- </span>
106
- <span className="text-muted-foreground/60">→</span>
107
- <span className="font-medium">{m.toName}</span>
108
- <span className="text-muted-foreground/60 ml-auto font-mono tabular-nums">
109
- {compactTimeAgo(fromSqliteTime(m.createdAt))}
110
- </span>
111
- </div>
112
- <p className="bg-foreground/[0.04] rounded-lg px-3 py-2 text-sm whitespace-pre-wrap">
113
- {m.body}
114
- </p>
115
- </div>
116
- ))}
117
- </div>
118
-
119
- <form
120
- onSubmit={(e) => {
121
- e.preventDefault();
122
- send.mutate(
123
- { to: recipient, body },
124
- { onSuccess: () => setBody("") }
125
- );
126
- }}
127
- className="space-y-2"
128
- >
129
- <Select
130
- value={recipient}
131
- onValueChange={(v) => {
132
- send.reset();
133
- setTo(v);
134
- }}
135
- >
136
- <SelectTrigger aria-label="Send to">
137
- <SelectValue placeholder="Send to a session..." />
138
- </SelectTrigger>
139
- <SelectContent>
140
- {peers.map((p) => (
141
- <SelectItem key={p.id} value={p.id}>
142
- {label(p)}
143
- {!p.running && " (not running)"}
144
- </SelectItem>
145
- ))}
146
- </SelectContent>
147
- </Select>
148
- <div className="flex gap-2">
149
- <Textarea
150
- aria-label="Message"
151
- value={body}
152
- onChange={(e) => setBody(e.target.value)}
153
- placeholder="Message as you"
154
- rows={2}
155
- className="min-h-11"
156
- />
157
- <Button
158
- type="submit"
159
- disabled={!recipient || !body.trim() || send.isPending}
160
- >
161
- Send
162
- </Button>
163
- </div>
164
- {send.error && (
165
- <p className="text-destructive text-xs">{send.error.message}</p>
166
- )}
167
- {send.data?.delivery.state === "failed" && (
168
- <p className="text-destructive text-xs">
169
- Saved to its inbox, but not delivered: {send.data.delivery.why}
170
- </p>
171
- )}
172
- </form>
173
- </DialogContent>
174
- </Dialog>
175
- );
176
- }
@@ -1,31 +0,0 @@
1
- "use client";
2
-
3
- import { BookOpen } from "lucide-react";
4
- import { Button } from "@/components/ui/button";
5
- import { useDocsWorkspace } from "@/data/lumifyhub/docs";
6
- import { docsUiActions } from "@/stores/docsUi";
7
-
8
- // Only for a project whose workspace is linked to LumifyHub.
9
- export function DocsButton({
10
- projectId,
11
- labelClassName,
12
- }: {
13
- projectId?: string | null;
14
- labelClassName?: string;
15
- }) {
16
- const workspace = useDocsWorkspace(projectId);
17
- if (!workspace) return null;
18
- return (
19
- <Button
20
- size="sm"
21
- variant="ghost"
22
- aria-label="Docs"
23
- title="Docs"
24
- className="h-7 px-2"
25
- onClick={() => docsUiActions.open(workspace.id)}
26
- >
27
- <BookOpen className="h-4 w-4" />
28
- <span className={labelClassName}>Docs</span>
29
- </Button>
30
- );
31
- }
@@ -1,86 +0,0 @@
1
- "use client";
2
-
3
- import { useState } from "react";
4
- import { AlertCircle, Bell, Settings } from "lucide-react";
5
- import { Button } from "./ui/button";
6
- import {
7
- DropdownMenu,
8
- DropdownMenuContent,
9
- DropdownMenuItem,
10
- DropdownMenuLabel,
11
- DropdownMenuSeparator,
12
- DropdownMenuTrigger,
13
- } from "./ui/dropdown-menu";
14
- import { cn } from "@/lib/utils";
15
- import type { NotificationSettings } from "@/lib/notifications";
16
- import { settingsUiActions } from "@/stores/settingsUi";
17
-
18
- interface WaitingSession {
19
- id: string;
20
- name: string;
21
- }
22
-
23
- // The bell: sessions waiting for you, and the way to Settings > Notifications.
24
- export function NotificationsMenu({
25
- settings,
26
- waitingSessions = [],
27
- onSelectSession,
28
- }: {
29
- settings: NotificationSettings;
30
- waitingSessions?: WaitingSession[];
31
- onSelectSession?: (id: string) => void;
32
- }) {
33
- const [open, setOpen] = useState(false);
34
- const waitingCount = waitingSessions.length;
35
-
36
- return (
37
- <DropdownMenu open={open} onOpenChange={setOpen}>
38
- <DropdownMenuTrigger asChild>
39
- <Button
40
- variant="ghost"
41
- size="icon-sm"
42
- aria-label="Notifications"
43
- className="relative h-7 w-7"
44
- >
45
- <Bell
46
- className={cn(
47
- "h-4 w-4",
48
- !settings.sound && "text-muted-foreground"
49
- )}
50
- />
51
- {waitingCount > 0 && (
52
- <span className="absolute -top-1 -right-1 flex h-4 w-4 items-center justify-center rounded-full bg-yellow-500 text-[10px] font-bold text-yellow-950">
53
- {waitingCount}
54
- </span>
55
- )}
56
- </Button>
57
- </DropdownMenuTrigger>
58
- <DropdownMenuContent align="end" className="w-52">
59
- {waitingCount > 0 && (
60
- <>
61
- <DropdownMenuLabel className="flex items-center gap-2 text-xs text-yellow-500">
62
- <AlertCircle className="h-3 w-3" />
63
- Waiting for input
64
- </DropdownMenuLabel>
65
- {waitingSessions.map((session) => (
66
- <DropdownMenuItem
67
- key={session.id}
68
- onClick={() => onSelectSession?.(session.id)}
69
- className="text-sm"
70
- >
71
- {session.name}
72
- </DropdownMenuItem>
73
- ))}
74
- <DropdownMenuSeparator />
75
- </>
76
- )}
77
- <DropdownMenuItem
78
- onClick={() => settingsUiActions.open("notifications")}
79
- >
80
- <Settings className="h-3 w-3" />
81
- Notification settings
82
- </DropdownMenuItem>
83
- </DropdownMenuContent>
84
- </DropdownMenu>
85
- );
86
- }
@@ -1,28 +0,0 @@
1
- "use client";
2
-
3
- import { ListTodo } from "lucide-react";
4
- import { Button } from "@/components/ui/button";
5
- import { tasksUiActions } from "@/stores/tasksUi";
6
- import { useTasksNeedingYou } from "./useTasksNeedingYou";
7
-
8
- export function TasksButton({ labelClassName }: { labelClassName?: string }) {
9
- const waiting = useTasksNeedingYou();
10
- return (
11
- <Button
12
- size="sm"
13
- variant="ghost"
14
- aria-label="Tasks"
15
- title="Tasks"
16
- className="h-7 px-2"
17
- onClick={tasksUiActions.openPanel}
18
- >
19
- <ListTodo className="h-4 w-4" />
20
- <span className={labelClassName}>Tasks</span>
21
- {waiting > 0 && (
22
- <span className="bg-primary text-primary-foreground rounded-full px-1.5 font-mono text-[10px] leading-4 tabular-nums">
23
- {waiting}
24
- </span>
25
- )}
26
- </Button>
27
- );
28
- }
@@ -1,75 +0,0 @@
1
- "use client";
2
-
3
- import { useSnapshot } from "valtio";
4
- import { Plus } from "lucide-react";
5
- import {
6
- Dialog,
7
- DialogContent,
8
- DialogHeader,
9
- DialogTitle,
10
- } from "@/components/ui/dialog";
11
- import { Button } from "@/components/ui/button";
12
- import { useTasksQuery } from "@/data/tasks";
13
- import { isFinished, needsHuman } from "@/lib/tasks/state";
14
- import { tasksUi, tasksUiActions } from "@/stores/tasksUi";
15
- import { newDraft } from "@/stores/drafts";
16
- import { TaskRow } from "./TaskRow";
17
- import { BoardTodoSection } from "@/components/LumifyHub";
18
- import { StacksSection } from "@/components/Stacks";
19
- import { FeaturesSection } from "@/components/Features";
20
-
21
- export function TasksDialog() {
22
- const { panelOpen } = useSnapshot(tasksUi);
23
- const { data: tasks = [], isPending } = useTasksQuery();
24
- const active = tasks.filter((t) => !isFinished(t.state));
25
- // Whatever needs a human first.
26
- active.sort(
27
- (a, b) => Number(needsHuman(b.state)) - Number(needsHuman(a.state))
28
- );
29
- const done = tasks.filter((t) => !active.includes(t)).slice(0, 10);
30
-
31
- return (
32
- <Dialog open={panelOpen} onOpenChange={tasksUiActions.setPanelOpen}>
33
- <DialogContent className="max-h-[85vh] max-w-xl overflow-y-auto [&>*]:min-w-0">
34
- <DialogHeader className="flex-row items-center justify-between gap-4 space-y-0">
35
- <DialogTitle>Tasks</DialogTitle>
36
- <Button
37
- size="sm"
38
- className="mr-6"
39
- onClick={() => {
40
- tasksUiActions.setPanelOpen(false);
41
- newDraft({ kind: "current", openPr: true });
42
- }}
43
- >
44
- <Plus className="h-4 w-4" />
45
- New task
46
- </Button>
47
- </DialogHeader>
48
- {isPending && (
49
- <div className="bg-muted/40 h-16 animate-pulse rounded-xl" />
50
- )}
51
- {!isPending && tasks.length === 0 && (
52
- <p className="text-muted-foreground py-6 text-center text-sm">
53
- No tasks yet. Start one and keep working while it runs.
54
- </p>
55
- )}
56
- <div className="space-y-2">
57
- {active.map((t) => (
58
- <TaskRow key={t.id} task={t} />
59
- ))}
60
- </div>
61
- <FeaturesSection />
62
- <StacksSection />
63
- <BoardTodoSection />
64
- {done.length > 0 && (
65
- <div className="space-y-2">
66
- <p className="label-mono text-muted-foreground pt-2">Finished</p>
67
- {done.map((t) => (
68
- <TaskRow key={t.id} task={t} />
69
- ))}
70
- </div>
71
- )}
72
- </DialogContent>
73
- </Dialog>
74
- );
75
- }
package/stores/busUi.ts DELETED
@@ -1,12 +0,0 @@
1
- import { proxy } from "valtio";
2
-
3
- export const busUi = proxy({ open: false });
4
-
5
- export const busUiActions = {
6
- setOpen: (open: boolean) => {
7
- busUi.open = open;
8
- },
9
- open: () => {
10
- busUi.open = true;
11
- },
12
- };
package/stores/tasksUi.ts DELETED
@@ -1,14 +0,0 @@
1
- import { proxy } from "valtio";
2
-
3
- // The Tasks panel's open state, shared by the header and the sidebar menu.
4
- // A new task is a draft with "Open a PR when done" (stores/drafts).
5
- export const tasksUi = proxy({ panelOpen: false });
6
-
7
- export const tasksUiActions = {
8
- openPanel: () => {
9
- tasksUi.panelOpen = true;
10
- },
11
- setPanelOpen: (open: boolean) => {
12
- tasksUi.panelOpen = open;
13
- },
14
- };