mirror of
https://github.com/oxynote/oxynote.git
synced 2026-10-17 02:49:18 +02:00
update script element ordering in AGENTS
This commit is contained in:
parent
196c188320
commit
361605c93d
13
AGENTS.md
13
AGENTS.md
|
|
@ -37,6 +37,19 @@ arbitrary and is not. Test: does it still earn its lines for someone reading
|
|||
the file a year from now with no idea what was asked? The same test applies
|
||||
to the prose in these AGENTS.md files.
|
||||
|
||||
Keep comments as simple and short as possible, so one read is enough:
|
||||
short sentences, plain words, no nested clauses or dashes. Good:
|
||||
|
||||
```ts
|
||||
// the simulation ends once the metric has data, but the cache may
|
||||
// still hold an empty result from before. Refetch, but only when the
|
||||
// query can run, because refetch does not check that itself.
|
||||
```
|
||||
|
||||
Bad: "the cached answer is keyed by the time range and not by the window it
|
||||
resolves to, so the one waiting here was taken while the metric was still
|
||||
missing — which is what the block has just been told it no longer is."
|
||||
|
||||
## 5. Goal-driven execution
|
||||
|
||||
Turn the task into a verifiable goal ("fix the bug" becomes a test that
|
||||
|
|
|
|||
|
|
@ -184,6 +184,9 @@ sentinel or a restructure over a non-null assertion.
|
|||
`CalendarInput.vue` → `CalendarInput.nuxt.test.ts`), never a mirror tree.
|
||||
A test spanning two files lives with the one that initiates the
|
||||
behaviour. Top-level `describe`s follow the source file's order.
|
||||
- **Test file shape**: top-level constants and variables above the first
|
||||
`describe`, never between tests; file-local helper functions below all
|
||||
tests, at the bottom.
|
||||
- **The suffix encodes the environment**, never `.spec.ts`, never an
|
||||
`@vitest-environment` pragma:
|
||||
|
||||
|
|
@ -336,9 +339,13 @@ test context).
|
|||
- **One assignment per statement** (`no-multi-assign`): no chaining, no
|
||||
assignment inside an expression such as `const group = (groups[key] ??=
|
||||
…)`. `??=` as its own statement is fine.
|
||||
- `<script setup>` order: imports; `defineProps`/`defineEmits`/`defineExpose`;
|
||||
store and composable initializations; `ref`/`computed`; lifecycle hooks;
|
||||
- `<script setup>` order: imports; compiler macros (`defineProps`,
|
||||
`defineEmits`, `defineModel`, `defineExpose`, …); store and composable
|
||||
initializations; `ref`/`computed` and plain variables; lifecycle hooks;
|
||||
watchers; functions.
|
||||
- Plain `.ts` modules follow the same shape: imports, then constants and
|
||||
module-level variables, then functions. No declarations between
|
||||
functions.
|
||||
- Tailwind utilities by default; custom rules go in `main.css` and are
|
||||
applied by class. No `<style>` blocks or static inline `style=`.
|
||||
|
||||
|
|
|
|||
|
|
@ -52,36 +52,6 @@ const queryCache = useQueryCache()
|
|||
const wsState = useWebSocketStateStore()
|
||||
let unsubWsHooksChange: (() => void) | null | undefined = null
|
||||
|
||||
// the hook menus refetch after their own writes, but a hook the assistant or
|
||||
// an MCP client adds arrives only on this topic: without it the indicators
|
||||
// would sit stale until the list expired
|
||||
watchImmediate(
|
||||
() => editorStore.activeDocumentId,
|
||||
(newId) => {
|
||||
unsubWsHooksChange?.()
|
||||
unsubWsHooksChange = null
|
||||
|
||||
if (!newId) {
|
||||
return
|
||||
}
|
||||
|
||||
unsubWsHooksChange = wsState.state?.subscribe(
|
||||
makeWsHooksChangeTopic(newId),
|
||||
(rawPayload) => {
|
||||
const payload = rawPayload as WSHooksChangePayload
|
||||
|
||||
void queryCache.invalidateQueries({
|
||||
key: DOCUMENT_HOOK_QUERY_KEYS.list(newId, payload.branchId),
|
||||
})
|
||||
},
|
||||
)
|
||||
},
|
||||
)
|
||||
|
||||
onUnmounted(() => {
|
||||
unsubWsHooksChange?.()
|
||||
})
|
||||
|
||||
const userCaretDetails = computed(() => {
|
||||
const caretColors = editorCaretColors()
|
||||
const randomIndex = Math.floor(Math.random() * caretColors.length)
|
||||
|
|
@ -136,8 +106,6 @@ import.meta.hot?.accept(() => {
|
|||
refreshGapDecorationsInBackground(contentEditor as Ref<Editor | null>)
|
||||
})
|
||||
|
||||
onBeforeUnmount(destroyBranchProviders)
|
||||
|
||||
// closing the tab never unmounts anything, so without this the provider is
|
||||
// only noticed as gone when its socket drops — and until then everyone else
|
||||
// still sees this user's caret. pagehide is the event that survives a tab
|
||||
|
|
@ -151,6 +119,38 @@ useEventListener(window, "pagehide", (event: PageTransitionEvent) => {
|
|||
destroyBranchProviders()
|
||||
})
|
||||
|
||||
onBeforeUnmount(destroyBranchProviders)
|
||||
|
||||
onUnmounted(() => {
|
||||
unsubWsHooksChange?.()
|
||||
})
|
||||
|
||||
// the hook menus refetch after their own writes, but a hook the assistant or
|
||||
// an MCP client adds arrives only on this topic: without it the indicators
|
||||
// would sit stale until the list expired
|
||||
watchImmediate(
|
||||
() => editorStore.activeDocumentId,
|
||||
(newId) => {
|
||||
unsubWsHooksChange?.()
|
||||
unsubWsHooksChange = null
|
||||
|
||||
if (!newId) {
|
||||
return
|
||||
}
|
||||
|
||||
unsubWsHooksChange = wsState.state?.subscribe(
|
||||
makeWsHooksChangeTopic(newId),
|
||||
(rawPayload) => {
|
||||
const payload = rawPayload as WSHooksChangePayload
|
||||
|
||||
void queryCache.invalidateQueries({
|
||||
key: DOCUMENT_HOOK_QUERY_KEYS.list(newId, payload.branchId),
|
||||
})
|
||||
},
|
||||
)
|
||||
},
|
||||
)
|
||||
|
||||
watchImmediate(
|
||||
[
|
||||
() => editorStore.preloadedBranchIds,
|
||||
|
|
|
|||
|
|
@ -73,6 +73,7 @@ const simulationPreset = computed(() => {
|
|||
// NOTE: the debounced config cannot be changed (the changes won't be saved)
|
||||
const debouncedConfig = ref(clone(props.config))
|
||||
watchDebounced(
|
||||
// keep the watch here because it acts as a config modifier
|
||||
() => props.config,
|
||||
(newV) => {
|
||||
debouncedConfig.value = clone(newV)
|
||||
|
|
@ -145,36 +146,12 @@ const fetchMetricData = useMultipleGenericQueries(
|
|||
// draw generated data for good.
|
||||
const clearedPreset = ref<MetricSimulationPreset | null>(null)
|
||||
|
||||
watch(simulationPreset, (preset, previous) => {
|
||||
if (preset) {
|
||||
clearedPreset.value = null
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if (previous) {
|
||||
clearedPreset.value = previous
|
||||
|
||||
// the cached answer for this query is keyed by the block's time
|
||||
// range and not by the window it resolves to, so the one waiting
|
||||
// here was taken while the metric was still missing — which is
|
||||
// what the block has just been told it no longer is. refetch
|
||||
// ignores whether the query may run, so it is asked only when it
|
||||
// may.
|
||||
if (queryRunnable.value) {
|
||||
void fetchMetricData.refetch()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// set on every refresh tick so the generated window re-resolves against
|
||||
// the current time and the chart slides forward like a live one
|
||||
const simulationNow = ref<Date>()
|
||||
|
||||
// a block can only simulate what it will later query, so the same
|
||||
// configuration a real query needs has to be in place first
|
||||
// the same rows the query needs: a block that could simulate but never
|
||||
// query would be stranded on generated data once the simulation lifted
|
||||
// a block simulates only when its real query could run. Otherwise it would
|
||||
// be stuck on generated data after the simulation ends.
|
||||
const queryRunnable = computed(() => {
|
||||
return canRunGenericQueries(
|
||||
props.config.dataSourceId,
|
||||
|
|
@ -187,7 +164,7 @@ const canSimulate = computed(() => {
|
|||
})
|
||||
|
||||
// what the block draws, which outlives the attribute by one load
|
||||
const drawnPreset = computed(() => {
|
||||
const drawnSimulationPreset = computed(() => {
|
||||
return (
|
||||
simulationPreset.value ??
|
||||
(fetchMetricData.asyncStatus.value === "loading"
|
||||
|
|
@ -219,7 +196,7 @@ const data = computed<{
|
|||
data?:
|
||||
MultipleLineChartData | MultipleBarChartData | MultipleGaugeChartData | null
|
||||
}>(() => {
|
||||
const preset = drawnPreset.value
|
||||
const preset = drawnSimulationPreset.value
|
||||
if (preset) {
|
||||
return mergeVisualizationResults(
|
||||
props.config.visualizationType,
|
||||
|
|
@ -257,6 +234,82 @@ const data = computed<{
|
|||
)
|
||||
})
|
||||
|
||||
useIntervalFn(
|
||||
() => {
|
||||
void refreshData()
|
||||
},
|
||||
() => refreshIntervalToMs(props.config.refreshInterval || RefreshInterval.M5),
|
||||
{
|
||||
immediate: true,
|
||||
immediateCallback: false,
|
||||
},
|
||||
)
|
||||
|
||||
const simulationCheck = useIntervalFn(
|
||||
() => {
|
||||
void checkSimulation()
|
||||
},
|
||||
SIMULATION_CHECK_INTERVAL_MS,
|
||||
{ immediate: false },
|
||||
)
|
||||
|
||||
onMounted(async () => {
|
||||
await refreshData()
|
||||
setTimeout(() => {
|
||||
canUseDebouncedConfigInParams.value = true
|
||||
}, 3000)
|
||||
})
|
||||
|
||||
watch(simulationPreset, (preset, previous) => {
|
||||
if (preset) {
|
||||
clearedPreset.value = null
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if (previous) {
|
||||
clearedPreset.value = previous
|
||||
|
||||
// the simulation ends once the metric has data, but the cache may
|
||||
// still hold an empty result from before. Refetch, but only when the
|
||||
// query can run, because refetch does not check that itself.
|
||||
if (queryRunnable.value) {
|
||||
void fetchMetricData.refetch()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// the document and branch ids arrive with the route, often after the
|
||||
// block has mounted, so the first check waits for them rather than being
|
||||
// skipped until the next tick
|
||||
watchImmediate(
|
||||
[
|
||||
simulationPreset,
|
||||
() => editorStore.activeDocumentId,
|
||||
() => editorStore.activeBranchId,
|
||||
],
|
||||
([preset]) => {
|
||||
if (!preset) {
|
||||
simulationCheck.pause()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
void checkSimulation()
|
||||
simulationCheck.resume()
|
||||
},
|
||||
)
|
||||
|
||||
watch(
|
||||
() => props.uid,
|
||||
() => {
|
||||
canUseDebouncedConfigInParams.value = false
|
||||
setTimeout(() => {
|
||||
canUseDebouncedConfigInParams.value = true
|
||||
}, 3000)
|
||||
},
|
||||
)
|
||||
|
||||
// labels series the query gave no legend format for, counting up per
|
||||
// merge call ("Line A", "Line B", ...)
|
||||
function orderedLegendLabel() {
|
||||
|
|
@ -283,63 +336,6 @@ function orderedLegendLabel() {
|
|||
}
|
||||
}
|
||||
|
||||
useIntervalFn(
|
||||
() => {
|
||||
void refreshData()
|
||||
},
|
||||
() => refreshIntervalToMs(props.config.refreshInterval || RefreshInterval.M5),
|
||||
{
|
||||
immediate: true,
|
||||
immediateCallback: false,
|
||||
},
|
||||
)
|
||||
|
||||
const simulationCheck = useIntervalFn(
|
||||
() => {
|
||||
void checkSimulation()
|
||||
},
|
||||
SIMULATION_CHECK_INTERVAL_MS,
|
||||
{ immediate: false },
|
||||
)
|
||||
|
||||
// the document and branch ids arrive with the route, often after the
|
||||
// block has mounted, so the first check waits for them rather than being
|
||||
// skipped until the next tick
|
||||
watchImmediate(
|
||||
[
|
||||
simulationPreset,
|
||||
() => editorStore.activeDocumentId,
|
||||
() => editorStore.activeBranchId,
|
||||
],
|
||||
([preset]) => {
|
||||
if (!preset) {
|
||||
simulationCheck.pause()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
void checkSimulation()
|
||||
simulationCheck.resume()
|
||||
},
|
||||
)
|
||||
|
||||
onMounted(async () => {
|
||||
await refreshData()
|
||||
setTimeout(() => {
|
||||
canUseDebouncedConfigInParams.value = true
|
||||
}, 3000)
|
||||
})
|
||||
|
||||
watch(
|
||||
() => props.uid,
|
||||
() => {
|
||||
canUseDebouncedConfigInParams.value = false
|
||||
setTimeout(() => {
|
||||
canUseDebouncedConfigInParams.value = true
|
||||
}, 3000)
|
||||
},
|
||||
)
|
||||
|
||||
async function refreshData() {
|
||||
if (
|
||||
!editorStore.activeDocumentId ||
|
||||
|
|
@ -421,7 +417,7 @@ function openModal() {
|
|||
<template>
|
||||
<div class="relative flex size-full min-w-0 items-center justify-center">
|
||||
<div
|
||||
v-if="drawnPreset"
|
||||
v-if="drawnSimulationPreset"
|
||||
class="pointer-events-none absolute top-2.5 left-1/2 z-1 flex -translate-x-1/2 items-center gap-1 text-2sm text-foreground/70"
|
||||
>
|
||||
<Icon name="lucide:hourglass" class="size-3" />
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user