Implement quick-add registry (task 21)
- Add url field to QuickAddAction; make action optional - Add getQuickAdds() + SerializedQuickAddItem to registry so quick-add data can cross the server→client RSC boundary without serializing fns - Update calendar/lists/notes manifests with navigation URLs - QuickAddProvider wraps root layout: holds sheet/palette open state and global cmd/ctrl+k shortcut - QuickAddSheet: bottom drawer on mobile, right-side popover on desktop, actions grouped by module - CommandPalette: cmdk-powered modal with fuzzy filter and full keyboard nav (arrows + enter + esc) - QuickAddFab: client button replacing the plain + stub in the dashboard - Add .claude/** to ESLint ignores to prevent stale worktree artifacts from failing lint All 4 E2E specs pass; typecheck, lint, and build clean. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
b9d3cce73a
commit
1133fa8aac
@@ -9,4 +9,5 @@ export type {
|
||||
SearchAdapter,
|
||||
SearchResult,
|
||||
} from "./module";
|
||||
export { registerModule, getRegistry, getEntityType, getWidget } from "./registry";
|
||||
export { registerModule, getRegistry, getEntityType, getWidget, getQuickAdds } from "./registry";
|
||||
export type { QuickAddItem, SerializedQuickAddItem } from "./registry";
|
||||
|
||||
@@ -45,7 +45,9 @@ export type QuickAddAction = {
|
||||
id: string;
|
||||
label: string;
|
||||
icon?: string;
|
||||
action: (ctx: WidgetContext) => void | Promise<void>;
|
||||
/** Navigation target used by the FAB sheet and command palette. */
|
||||
url: string;
|
||||
action?: (ctx: WidgetContext) => void | Promise<void>;
|
||||
};
|
||||
|
||||
export type EntityTypeRegistration = {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { ModuleManifest, EntityTypeRegistration, DashboardWidget } from "./module";
|
||||
import type { ModuleManifest, EntityTypeRegistration, DashboardWidget, QuickAddAction } from "./module";
|
||||
|
||||
const modules = new Map<string, ModuleManifest>();
|
||||
const entityTypes = new Map<string, EntityTypeRegistration>();
|
||||
@@ -29,3 +29,28 @@ export function getEntityType(type: string): EntityTypeRegistration | undefined
|
||||
export function getWidget(id: string): DashboardWidget | undefined {
|
||||
return widgets.get(id);
|
||||
}
|
||||
|
||||
export type QuickAddItem = QuickAddAction & { moduleId: string; moduleName: string };
|
||||
|
||||
/** Serializable subset safe to pass from server components to client components. */
|
||||
export type SerializedQuickAddItem = {
|
||||
id: string;
|
||||
label: string;
|
||||
icon?: string;
|
||||
url: string;
|
||||
moduleId: string;
|
||||
moduleName: string;
|
||||
};
|
||||
|
||||
export function getQuickAdds(): SerializedQuickAddItem[] {
|
||||
return [...modules.values()].flatMap((manifest) =>
|
||||
(manifest.quickAdds ?? []).map(({ id, label, icon, url }) => ({
|
||||
id,
|
||||
label,
|
||||
icon,
|
||||
url,
|
||||
moduleId: manifest.id,
|
||||
moduleName: manifest.name,
|
||||
})),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -156,13 +156,13 @@ const manifest: ModuleManifest = {
|
||||
id: "calendar.new-event",
|
||||
label: "New event",
|
||||
icon: "calendar-plus",
|
||||
action: () => undefined,
|
||||
url: "/calendar",
|
||||
},
|
||||
{
|
||||
id: "calendar.new-calendar",
|
||||
label: "New calendar",
|
||||
icon: "calendar-days",
|
||||
action: () => undefined,
|
||||
url: "/calendar",
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import type { ModuleManifest, WidgetContext } from "../_core/module";
|
||||
import { z } from "zod";
|
||||
import { addItemToDefaultList } from "./server/actions";
|
||||
import { listLists, listWidgetItems, searchItems, searchLists } from "./server/queries";
|
||||
|
||||
const listIdsSchema = z.union([z.literal("all"), z.array(z.string().uuid())]);
|
||||
@@ -90,23 +89,19 @@ const manifest: ModuleManifest = {
|
||||
id: "lists.add-shopping",
|
||||
label: "Add to shopping",
|
||||
icon: "shopping-cart",
|
||||
action: async () => {
|
||||
await addItemToDefaultList({ type: "shopping", text: "New item" });
|
||||
},
|
||||
url: "/lists",
|
||||
},
|
||||
{
|
||||
id: "lists.add-task",
|
||||
label: "Add to tasks",
|
||||
icon: "list-checks",
|
||||
action: async () => {
|
||||
await addItemToDefaultList({ type: "task", text: "New task" });
|
||||
},
|
||||
url: "/lists",
|
||||
},
|
||||
{
|
||||
id: "lists.new-list",
|
||||
label: "New list",
|
||||
icon: "list-plus",
|
||||
action: () => undefined,
|
||||
url: "/lists",
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
@@ -67,7 +67,7 @@ const manifest: ModuleManifest = {
|
||||
id: "notes.new-note",
|
||||
label: "New note",
|
||||
icon: "file-plus",
|
||||
action: () => undefined,
|
||||
url: "/notes/new",
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user