diff --git a/frontend/src/api/inception.ts b/frontend/src/api/inception.ts new file mode 100644 index 0000000..05773e1 --- /dev/null +++ b/frontend/src/api/inception.ts @@ -0,0 +1,42 @@ +/** Project inception (milestone 297): what a project was decided to inherit. */ +import { apiGet, apiPost } from "@/api/client"; + +export interface InceptionChoices { + exclude_always_on_rulebooks: number[]; + subscribe_rulebooks: number[]; + design_system_id: number | null; + seed_systems: boolean; +} + +export interface InceptionRecord { + decided_at: string; + decided_by: number | null; + via: "mcp" | "ui" | "legacy"; + choices: InceptionChoices; +} + +export interface InceptionDefaults { + always_on_rulebooks: { id: number; title: string }[]; + other_rulebooks: { id: number; title: string }[]; + excluded_always_on: { id: number; title: string }[]; + subscribed_rulebooks: { id: number; title: string }[]; + design_system_id: number | null; + design_systems: { id: number; title: string }[]; + systems: number; +} + +export interface InceptionDecision { + project_id: number; + inception: InceptionRecord; + effects: { excluded: number[]; subscribed: number[]; design_system_id: number | null; systems_seeded: string[] }; +} + +export const emptyChoices = (): InceptionChoices => ({ + exclude_always_on_rulebooks: [], subscribe_rulebooks: [], design_system_id: null, seed_systems: false, +}); + +export const fetchInceptionDefaults = (projectId: number) => + apiGet(`/api/projects/${projectId}/inception/defaults`); + +export const decideInception = (projectId: number, choices: InceptionChoices) => + apiPost(`/api/projects/${projectId}/inception`, { choices }); diff --git a/frontend/src/api/rulebooks.ts b/frontend/src/api/rulebooks.ts index 858d854..1c52447 100644 --- a/frontend/src/api/rulebooks.ts +++ b/frontend/src/api/rulebooks.ts @@ -71,6 +71,8 @@ export interface ApplicableRules { }[]; truncated: boolean; subscribed_rulebooks: { id: number; title: string }[]; + /** Always-on rulebooks this project opted out of at inception (milestone 297). */ + excluded_always_on: { id: number; title: string }[]; } // ── Rulebooks ─────────────────────────────────────────────────────── @@ -181,3 +183,14 @@ export async function suppressTopicForProject(projectId: number, topicId: number export async function unsuppressTopicForProject(projectId: number, topicId: number): Promise { return apiDelete(`/api/projects/${projectId}/suppressions/topics/${topicId}`); } + +// ── Always-on exclusions (milestone 297) ──────────────────────────────────── + +export async function excludeAlwaysOnRulebook(projectId: number, rulebookId: number): Promise { + await apiPost(`/api/projects/${projectId}/exclusions/rulebooks/${rulebookId}`, {}); +} + +export async function includeAlwaysOnRulebook(projectId: number, rulebookId: number): Promise { + await apiDelete(`/api/projects/${projectId}/exclusions/rulebooks/${rulebookId}`); +} + diff --git a/frontend/src/components/InceptionCard.vue b/frontend/src/components/InceptionCard.vue new file mode 100644 index 0000000..b37699b --- /dev/null +++ b/frontend/src/components/InceptionCard.vue @@ -0,0 +1,189 @@ + + + + + diff --git a/frontend/src/components/rules/ProjectRulesTab.vue b/frontend/src/components/rules/ProjectRulesTab.vue index 4749279..cd989b0 100644 --- a/frontend/src/components/rules/ProjectRulesTab.vue +++ b/frontend/src/components/rules/ProjectRulesTab.vue @@ -2,10 +2,18 @@ import { ref, onMounted, watch } from "vue"; import { useRouter } from "vue-router"; import { - getProjectApplicableRules, subscribeProject, unsubscribeProject, - listRulebooks, getRule, createProjectRule, deleteRule, - suppressRuleForProject, unsuppressRuleForProject, - suppressTopicForProject, unsuppressTopicForProject, + getProjectApplicableRules, + subscribeProject, + unsubscribeProject, + listRulebooks, + getRule, + createProjectRule, + deleteRule, + suppressRuleForProject, + unsuppressRuleForProject, + suppressTopicForProject, + unsuppressTopicForProject, + includeAlwaysOnRulebook, } from "@/api/rulebooks"; import type { ApplicableRules, Rulebook } from "@/api/rulebooks"; @@ -35,6 +43,11 @@ async function subscribe(rulebookId: number) { await load(); } +async function includeBack(rulebookId: number) { + await includeAlwaysOnRulebook(props.projectId, rulebookId); + await load(); +} + async function unsubscribe(rulebookId: number) { if (!confirm("Unsubscribe from this rulebook for this project?")) return; await unsubscribeProject(props.projectId, rulebookId); @@ -172,6 +185,17 @@ watch(() => props.projectId, load); +
+

Excluded always-on rulebooks

+

Opted out at inception — these do not bind this project.

+
+ + {{ rb.title }} + + +
+
+

Project rules

@@ -321,6 +345,9 @@ watch(() => props.projectId, load);