feat(inception): UI — New-project modal step 2, InceptionCard on the project page, Rules tab shows excluded always-on rulebooks; REST exclusion routes (#2883, milestone 297 step 5)
CI & Build / Python lint (push) Successful in 3s
CI & Build / Plugin hooks (push) Successful in 8s
CI & Build / integration (push) Successful in 21s
CI & Build / TypeScript typecheck (push) Successful in 34s
CI & Build / Python tests (push) Failing after 42s
CI & Build / Build & push image (push) Skipped
CI & Build / Python lint (push) Successful in 3s
CI & Build / Plugin hooks (push) Successful in 8s
CI & Build / integration (push) Successful in 21s
CI & Build / TypeScript typecheck (push) Successful in 34s
CI & Build / Python tests (push) Failing after 42s
CI & Build / Build & push image (push) Skipped
- components/InceptionCard.vue: the one form, two homes — mode="create" in the New-project modal's second step (emits the choices; the create carries `inception`), mode="decide" on ProjectView for the owner of an undecided project (loads that project's defaults, records the decision). Always-on rulebooks listed checked (uncheck = exclude), others unchecked (check = subscribe), design system select, seed-Systems toggle (disabled once the project has Systems). Tokens only; modal canon (#2855); .btn-* canon. - ProjectView: the card while undecided, one "Inheritance decided <date> via … · …" line after; onDecided refreshes the project. - ProjectRulesTab: "Excluded always-on rulebooks" section with include-back. - api/inception.ts (types, fetchInceptionDefaults, decideInception); api/rulebooks.ts: ApplicableRules.excluded_always_on, exclude/include wrappers; REST POST/DELETE /api/projects/<id>/exclusions/rulebooks/<rb>. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,7 +1,9 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, onMounted } from "vue";
|
||||
import { useRouter } from "vue-router";
|
||||
import { apiGet, apiPost } from "@/api/client";
|
||||
import { apiGet, apiPost, apiErrorMessage } from "@/api/client";
|
||||
import { emptyChoices, type InceptionChoices } from "@/api/inception";
|
||||
import InceptionCard from "@/components/InceptionCard.vue";
|
||||
import { useToastStore } from "@/stores/toast";
|
||||
import { milestoneColor } from "@/utils/palette";
|
||||
|
||||
@@ -47,6 +49,9 @@ const newTitle = ref("");
|
||||
const newDescription = ref("");
|
||||
const newGoal = ref("");
|
||||
const creating = ref(false);
|
||||
// Step 2 of the modal (milestone 297): what the new project inherits.
|
||||
const modalStep = ref<1 | 2>(1);
|
||||
const newInception = ref<InceptionChoices>(emptyChoices());
|
||||
|
||||
const filteredProjects = computed(() => {
|
||||
if (activeTab.value === "all") return projects.value;
|
||||
@@ -73,6 +78,8 @@ function openNewProjectModal() {
|
||||
newTitle.value = "";
|
||||
newDescription.value = "";
|
||||
newGoal.value = "";
|
||||
modalStep.value = 1;
|
||||
newInception.value = emptyChoices();
|
||||
showNewProjectModal.value = true;
|
||||
}
|
||||
|
||||
@@ -88,13 +95,15 @@ async function createProject() {
|
||||
title: newTitle.value.trim(),
|
||||
description: newDescription.value.trim() || undefined,
|
||||
goal: newGoal.value.trim() || undefined,
|
||||
// The decision rides the create: a project made here is never undecided.
|
||||
inception: newInception.value,
|
||||
});
|
||||
projects.value.unshift(project);
|
||||
showNewProjectModal.value = false;
|
||||
toast.show("Project created");
|
||||
router.push(`/projects/${project.id}`);
|
||||
} catch {
|
||||
toast.show("Failed to create project", "error");
|
||||
} catch (e: unknown) {
|
||||
toast.show(apiErrorMessage(e, "Failed to create project"), "error");
|
||||
} finally {
|
||||
creating.value = false;
|
||||
}
|
||||
@@ -266,8 +275,9 @@ function overallPct(project: Project): { total: number; pct: number } {
|
||||
<teleport to="body">
|
||||
<div v-if="showNewProjectModal" class="modal-overlay" @click.self="closeModal">
|
||||
<div class="modal-card">
|
||||
<h3 class="modal-title">New Project</h3>
|
||||
<div class="modal-field">
|
||||
<h3 class="modal-title">{{ modalStep === 1 ? "New Project" : "New Project — what it inherits" }}</h3>
|
||||
<InceptionCard v-if="modalStep === 2" mode="create" v-model:choices="newInception" />
|
||||
<div v-if="modalStep === 1" class="modal-field">
|
||||
<label>Title <span class="required">*</span></label>
|
||||
<input
|
||||
v-model="newTitle"
|
||||
@@ -275,11 +285,11 @@ function overallPct(project: Project): { total: number; pct: number } {
|
||||
class="modal-input"
|
||||
placeholder="Project title"
|
||||
autofocus
|
||||
@keydown.enter="createProject"
|
||||
@keydown.enter="modalStep = 2"
|
||||
@keydown.escape="closeModal"
|
||||
/>
|
||||
</div>
|
||||
<div class="modal-field">
|
||||
<div v-if="modalStep === 1" class="modal-field">
|
||||
<label>Goal</label>
|
||||
<input
|
||||
v-model="newGoal"
|
||||
@@ -289,7 +299,7 @@ function overallPct(project: Project): { total: number; pct: number } {
|
||||
@keydown.escape="closeModal"
|
||||
/>
|
||||
</div>
|
||||
<div class="modal-field">
|
||||
<div v-if="modalStep === 1" class="modal-field">
|
||||
<label>Description</label>
|
||||
<textarea
|
||||
v-model="newDescription"
|
||||
@@ -301,7 +311,17 @@ function overallPct(project: Project): { total: number; pct: number } {
|
||||
</div>
|
||||
<div class="modal-actions">
|
||||
<button class="modal-btn" @click="closeModal">Cancel</button>
|
||||
<button v-if="modalStep === 2" class="modal-btn" @click="modalStep = 1">Back</button>
|
||||
<button
|
||||
v-if="modalStep === 1"
|
||||
class="modal-btn modal-btn-primary"
|
||||
@click="modalStep = 2"
|
||||
:disabled="!newTitle.trim()"
|
||||
>
|
||||
Next
|
||||
</button>
|
||||
<button
|
||||
v-else
|
||||
class="modal-btn modal-btn-primary"
|
||||
@click="createProject"
|
||||
:disabled="!newTitle.trim() || creating"
|
||||
|
||||
Reference in New Issue
Block a user