- care reminders now fire with plant-specific body ("Time to water Pothos")
via title/body columns on the reminders table (migration 0016)
- bi-directional task sync: checking off a garden-linked task list item
creates a care log; logging care from garden marks the linked task done
(list_items.metadata stores gardenPlantId + gardenCareType linkage)
- upload error response no longer leaks debug detail; error message
corrected from "5 MB" to "100 MB"
108 lines
2.9 KiB
TypeScript
108 lines
2.9 KiB
TypeScript
import type {
|
|
ModuleManifest,
|
|
EntityTypeRegistration,
|
|
DashboardWidget,
|
|
QuickAddAction,
|
|
} from "./module";
|
|
|
|
const modules = new Map<string, ModuleManifest>();
|
|
const entityTypes = new Map<string, EntityTypeRegistration>();
|
|
const widgets = new Map<string, DashboardWidget>();
|
|
|
|
export function registerModule(manifest: ModuleManifest): void {
|
|
modules.set(manifest.id, manifest);
|
|
for (const entity of manifest.entities) {
|
|
entityTypes.set(entity.type, entity);
|
|
}
|
|
for (const widget of manifest.dashboardWidgets ?? []) {
|
|
widgets.set(widget.id, widget);
|
|
}
|
|
}
|
|
|
|
export function getRegistry() {
|
|
return Object.freeze({
|
|
modules: Object.freeze([...modules.values()]),
|
|
entityTypes: Object.freeze([...entityTypes.values()]),
|
|
widgets: Object.freeze([...widgets.values()]),
|
|
});
|
|
}
|
|
|
|
export function getEntityType(type: string): EntityTypeRegistration | undefined {
|
|
return entityTypes.get(type);
|
|
}
|
|
|
|
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 type SerializedWidgetMeta = {
|
|
id: string;
|
|
title: string;
|
|
description: string;
|
|
category?: string;
|
|
defaultSize: { w: number; h: number };
|
|
minSize?: { w: number; h: number };
|
|
maxSize?: { w: number; h: number };
|
|
defaultConfig: unknown;
|
|
};
|
|
|
|
export function getWidgetMetas(): SerializedWidgetMeta[] {
|
|
return [...widgets.values()].map(
|
|
({ id, title, description, category, defaultSize, minSize, maxSize, defaultConfig }) => ({
|
|
id,
|
|
title,
|
|
description,
|
|
category,
|
|
defaultSize,
|
|
minSize,
|
|
maxSize,
|
|
defaultConfig,
|
|
}),
|
|
);
|
|
}
|
|
|
|
// ─── Item toggle hooks ────────────────────────────────────────────────────────
|
|
|
|
type ItemTogglePayload = {
|
|
itemId: string;
|
|
metadata: Record<string, unknown>;
|
|
done: boolean;
|
|
userId: string;
|
|
};
|
|
type ItemToggleHook = (payload: ItemTogglePayload) => Promise<void>;
|
|
|
|
const itemToggleHooks = new Map<string, ItemToggleHook>();
|
|
|
|
export function registerItemToggleHook(id: string, hook: ItemToggleHook): void {
|
|
itemToggleHooks.set(id, hook);
|
|
}
|
|
|
|
export async function fireItemToggleHooks(payload: ItemTogglePayload): Promise<void> {
|
|
await Promise.allSettled([...itemToggleHooks.values()].map((h) => h(payload)));
|
|
}
|
|
|
|
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,
|
|
})),
|
|
);
|
|
}
|