Edit flow configured

This commit is contained in:
adilallo
2026-04-29 18:29:16 -06:00
parent 3a9727bceb
commit fc845d8308
39 changed files with 681 additions and 165 deletions
+2 -2
View File
@@ -292,8 +292,8 @@ export type MyPublishedRule = {
};
/**
* Lists the signed-in users published rules (newest first). Returns `null` on
* network failure or unauthenticated response.
* Lists the signed-in users published rules (**last updated first**, stable by id).
* Returns `null` on network failure or unauthenticated response.
*/
export async function fetchMyPublishedRules(): Promise<
MyPublishedRule[] | null
+2 -1
View File
@@ -1,6 +1,7 @@
/**
* Bridges final-review → completed without query strings, and re-opens a rule
* from profile (`/create/completed?ruleId=…`) after GET /api/rules/[id].
* from profile (`/create/completed?ruleId=…`) after GET /api/rules/[id]. Profile
* "Manage" links here; "View" uses `/rules/[id]`.
*/
export const CREATE_FLOW_LAST_PUBLISHED_KEY = "createFlow.lastPublished";
+67
View File
@@ -0,0 +1,67 @@
/**
* Reorders facet-ranked method presets so explicitly confirmed selections pin
* to the top while the remainder keeps score-based ranking (recommended before
* default).
*/
/** Selected ids first (selection array order); then tail in `ranked` order. */
export function orderRankedMethodsWithPinnedSelection<T extends { id: string }>(
rankedMethods: readonly T[],
selectedIds: readonly string[],
pinActive: boolean,
): T[] {
if (!pinActive || selectedIds.length === 0) {
return [...rankedMethods];
}
const byId = new Map(rankedMethods.map((m) => [m.id, m] as const));
const head: T[] = [];
const picked = new Set<string>();
for (const id of selectedIds) {
const row = byId.get(id);
if (!row || picked.has(id)) continue;
picked.add(id);
head.push(row);
}
const tail = rankedMethods.filter((m) => !picked.has(m.id));
return [...head, ...tail];
}
/**
* Prefer selected ids in compact slots (up to `limit`), then facet-derived
* `baseCompact.compactCardIds`, then remaining methods in showcase order so
* selected cards surface even when they are outside the unpinned facet top-N.
*/
export function mergeCompactCardIdsWithPinnedSelected(
showcaseOrderIds: readonly string[],
baseCompactCardIds: readonly string[],
selectedIds: readonly string[],
pinActive: boolean,
limit: number,
): string[] {
if (!pinActive || selectedIds.length === 0) {
return [...baseCompactCardIds].slice(0, limit);
}
const valid = new Set(showcaseOrderIds);
const out: string[] = [];
const seen = new Set<string>();
for (const id of selectedIds) {
if (out.length >= limit) break;
if (!valid.has(id) || seen.has(id)) continue;
seen.add(id);
out.push(id);
}
for (const id of baseCompactCardIds) {
if (out.length >= limit) break;
if (!valid.has(id) || seen.has(id)) continue;
seen.add(id);
out.push(id);
}
for (const id of showcaseOrderIds) {
if (out.length >= limit) break;
if (seen.has(id)) continue;
seen.add(id);
out.push(id);
}
return out;
}
+3 -2
View File
@@ -65,7 +65,8 @@ export type OwnerPublishedRuleListItem = {
};
/**
* Lists published rules owned by the given user (alphabetical by title, then id).
* Lists published rules owned by the given user (**most recently updated first**,
* then stable `id` tie-break).
* Returns `null` when the database is not configured or the query throws.
*/
export async function listPublishedRulesForUser(
@@ -79,7 +80,7 @@ export async function listPublishedRulesForUser(
try {
return await prisma.publishedRule.findMany({
where: { userId },
orderBy: [{ title: "asc" }, { id: "asc" }],
orderBy: [{ updatedAt: "desc" }, { id: "asc" }],
take: clamped,
select: PUBLISHED_RULE_OWNER_LIST_SELECT,
});
@@ -112,6 +112,15 @@ export const createFlowStateSchema = z
conflictManagementDetailsById: z
.record(conflictManagementDetailEntrySchema)
.optional(),
methodSectionsPinCommitted: z
.object({
communication: z.boolean().optional(),
membership: z.boolean().optional(),
decisionApproaches: z.boolean().optional(),
conflictManagement: z.boolean().optional(),
})
.strict()
.optional(),
pendingTemplateAction: z
.object({
slug: z.string().max(200),
@@ -13,6 +13,12 @@ export type GovernanceTemplateCatalogEntry = {
backgroundColor: string;
/** Path under public/ for getAssetPath() — Figma Asset / Template Mark */
iconPath: string;
/**
* When true, the templates grid shows the “RECOMMENDED” tag (facet-based
* scores will set this in `ruleTemplateToGridEntry` when wired; catalog
* entries omit unless intentionally static).
*/
recommended?: boolean;
};
/** SVGs in `public/assets/template-mark/<slug>.svg` (kebab-case slug). */
+11 -2
View File
@@ -11,16 +11,24 @@ import {
export const TEMPLATE_GRID_FALLBACK_PRESENTATION = {
iconPath: governanceTemplateIconPath("consensus"),
backgroundColor: "bg-[var(--color-surface-invert-brand-teal)]",
recommended: false,
} as const;
export type TemplateGridCardEntry = GovernanceTemplateCatalogEntry;
function presentationForSlug(slug: string): Pick<
GovernanceTemplateCatalogEntry,
"iconPath" | "backgroundColor"
"iconPath" | "backgroundColor" | "recommended"
> {
const catalog = getGovernanceTemplateCatalogEntry(slug);
return catalog ?? TEMPLATE_GRID_FALLBACK_PRESENTATION;
if (catalog) {
return {
iconPath: catalog.iconPath,
backgroundColor: catalog.backgroundColor,
recommended: catalog.recommended === true,
};
}
return TEMPLATE_GRID_FALLBACK_PRESENTATION;
}
/**
@@ -35,6 +43,7 @@ export function ruleTemplateToGridEntry(template: RuleTemplateDto): TemplateGrid
description,
iconPath: pres.iconPath,
backgroundColor: pres.backgroundColor,
recommended: pres.recommended,
};
}