CI & Build / Python lint (push) Successful in 3s
CI & Build / Plugin hooks (push) Successful in 16s
CI & Build / integration (push) Successful in 23s
CI & Build / TypeScript typecheck (push) Successful in 25s
CI & Build / Python tests (push) Successful in 57s
CI & Build / Build & push image (push) Successful in 43s
#2277 counted ~150 "raw colour literals bypassing the tokens". Measuring them told a different story: 184 sat in `var(--token, #fallback)` position, and a check against theme.css shows every one of those tokens IS declared. So the fallbacks could not render. Not drift — vestigial. They were also not this palette. The most common were Tailwind and Flat-UI defaults — #6366f1 indigo, #22c55e green, #f59e0b amber, #3b82f6 blue, #e74c3c and #27ae60 — a second, unsanctioned colour scheme sitting in the codebase looking like the app's colours to anyone reading it. Removing them is not tidying. #2319's lesson is that a fallback is WORSE than a missing token: a missing token renders as nothing and someone eventually notices, while a fallback renders something plausible forever. These 184 were one token rename away from silently repainting the app in Tailwind. The design token check would catch the rename — but the fallback is precisely the thing that would make it invisible if the check were ever bypassed. Literal count 152 -> 45, which matters beyond the number: a report that is mostly unreachable noise is one people stop reading, and then it stops working while still passing. What remains should be genuinely worth looking at. Done with a paren-aware transform, not a regex — `var(--x, rgba(0,0,0,.5))` nests parens and `[^)]+` would cut at the first one and leave `))` behind. Verified after: every changed line is a fallback strip and nothing else, and every var() reference still resolves. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UaYUaouG9jjhATyuxCKrQs
119 lines
3.4 KiB
Vue
119 lines
3.4 KiB
Vue
<script setup lang="ts">
|
|
import { onMounted, ref, watch } from "vue";
|
|
import { useRoute, useRouter } from "vue-router";
|
|
import { useRulebooksStore } from "@/stores/rulebooks";
|
|
import RulebookListPane from "@/components/rules/RulebookListPane.vue";
|
|
import RulebookDetailPane from "@/components/rules/RulebookDetailPane.vue";
|
|
import RuleListPane from "@/components/rules/RuleListPane.vue";
|
|
import RuleEditorSlideOver from "@/components/rules/RuleEditorSlideOver.vue";
|
|
|
|
const store = useRulebooksStore();
|
|
const route = useRoute();
|
|
const router = useRouter();
|
|
|
|
const selectedRulebookId = ref<number | null>(null);
|
|
const selectedTopicId = ref<number | null>(null);
|
|
const editingRuleId = ref<number | null>(null);
|
|
const creatingRuleForTopic = ref<number | null>(null);
|
|
|
|
function syncFromRoute() {
|
|
const rb = route.query.rb ? Number(route.query.rb) : null;
|
|
const topic = route.query.topic ? Number(route.query.topic) : null;
|
|
const rule = route.query.rule ? Number(route.query.rule) : null;
|
|
selectedRulebookId.value = rb;
|
|
selectedTopicId.value = topic;
|
|
editingRuleId.value = rule;
|
|
}
|
|
|
|
function selectRulebook(id: number) {
|
|
selectedRulebookId.value = id;
|
|
selectedTopicId.value = null;
|
|
router.replace({ query: { rb: String(id) } });
|
|
store.fetchTopics(id);
|
|
}
|
|
|
|
function selectTopic(id: number) {
|
|
selectedTopicId.value = id;
|
|
router.replace({ query: { ...route.query, topic: String(id) } });
|
|
store.fetchRules(id);
|
|
}
|
|
|
|
function openRule(id: number) {
|
|
editingRuleId.value = id;
|
|
router.replace({ query: { ...route.query, rule: String(id) } });
|
|
}
|
|
|
|
function closeRuleEditor() {
|
|
editingRuleId.value = null;
|
|
creatingRuleForTopic.value = null;
|
|
const { rule, ...rest } = route.query;
|
|
void rule;
|
|
router.replace({ query: rest });
|
|
}
|
|
|
|
function startCreatingRule(topicId: number) {
|
|
creatingRuleForTopic.value = topicId;
|
|
}
|
|
|
|
onMounted(async () => {
|
|
await store.fetchRulebooks();
|
|
syncFromRoute();
|
|
if (selectedRulebookId.value) await store.fetchTopics(selectedRulebookId.value);
|
|
if (selectedTopicId.value) await store.fetchRules(selectedTopicId.value);
|
|
});
|
|
|
|
watch(() => route.query, syncFromRoute);
|
|
</script>
|
|
|
|
<template>
|
|
<div class="rules-view">
|
|
<RulebookListPane
|
|
:rulebooks="store.rulebooks"
|
|
:selected-id="selectedRulebookId"
|
|
@select="selectRulebook"
|
|
/>
|
|
<RulebookDetailPane
|
|
v-if="selectedRulebookId !== null"
|
|
:rulebook-id="selectedRulebookId"
|
|
:topics="store.topicsByRulebook[selectedRulebookId] || []"
|
|
:selected-topic-id="selectedTopicId"
|
|
@select-topic="selectTopic"
|
|
/>
|
|
<div v-else class="pane empty">
|
|
<p>Select a rulebook to view its topics.</p>
|
|
</div>
|
|
<RuleListPane
|
|
v-if="selectedTopicId !== null"
|
|
:topic-id="selectedTopicId"
|
|
:rules="store.rulesByTopic[selectedTopicId] || []"
|
|
@open-rule="openRule"
|
|
@create-rule="startCreatingRule"
|
|
/>
|
|
<div v-else class="pane empty">
|
|
<p>Select a topic to view its rules.</p>
|
|
</div>
|
|
<RuleEditorSlideOver
|
|
v-if="editingRuleId !== null || creatingRuleForTopic !== null"
|
|
:rule-id="editingRuleId"
|
|
:topic-id="creatingRuleForTopic"
|
|
@close="closeRuleEditor"
|
|
/>
|
|
</div>
|
|
</template>
|
|
|
|
<style scoped>
|
|
.rules-view {
|
|
display: grid;
|
|
grid-template-columns: 280px 300px 1fr;
|
|
height: 100vh;
|
|
gap: 1px;
|
|
background: var(--color-border);
|
|
}
|
|
.pane.empty {
|
|
background: var(--color-surface);
|
|
padding: 1rem;
|
|
opacity: 0.6;
|
|
font-style: italic;
|
|
}
|
|
</style>
|