fix(ui): prevent layerchart circular dependency in thumbs submissions

This commit is contained in:
Rotimi Best
2026-08-04 16:56:09 +01:00
parent ee4d4e031d
commit 753f9b3d7b
2 changed files with 39 additions and 12 deletions
+31
View File
@@ -431,6 +431,37 @@ When cleanup or reset must follow a specific lifecycle moment, use the matching
Use `.server.ts` files for server-side code to isolate API keys.
### Chart Imports and SSR
`layerchart` has internal circular dependencies that Vite cannot reliably evaluate during SSR. Never statically import `layerchart` or the `@cio/ui/base/chart` runtime barrel from a component that can enter the server module graph.
- Import chart types from the dependency-free types module: `import type { ChartConfig } from '@cio/ui/base/chart/types'` (or the equivalent relative path inside `packages/ui`).
- Load chart runtime components with a browser-only dynamic import and render them inside an `{#await}` block guarded by `browser` or `typeof window !== 'undefined'`.
- In exercise submission renderers, reuse `submission-response-bar-chart.svelte` or `submission-response-pie-chart.svelte`; do not import the chart barrel directly.
```svelte
<script module lang="ts">
function loadChart() {
if (typeof window === 'undefined') return Promise.reject(new Error('browser-only'));
return import('@cio/ui/base/chart');
}
</script>
<script lang="ts">
import { browser } from '$app/environment';
import type { ChartConfig } from '@cio/ui/base/chart/types';
</script>
{#if browser}
{#await loadChart() then Chart}
<Chart.ChartContainer {config}>
<Chart.BarChart data={chartData} x="label" y="value" />
</Chart.ChartContainer>
{/await}
{/if}
```
### UI Components
- Add new UI components under `packages/ui/src` following existing folder patterns.
@@ -1,7 +1,8 @@
<script lang="ts">
import * as Chart from '../../../../base/chart';
import type { ExerciseQuestionRendererProps } from '@cio/question-types';
import type { ChartConfig } from '../../../../base/chart/types';
import SubmissionResponsePieChart from '../shared/submission-response-pie-chart.svelte';
import { getAnswerForQuestion, getSubmissionLabel, normalizeLabel, withChartColors } from '../submission-utils';
let {
@@ -30,7 +31,7 @@
label: getSubmissionLabel(labels, 'submission.chart.responses', 'Responses'),
color: 'var(--chart-1)'
}
} satisfies Chart.ChartConfig);
} satisfies ChartConfig);
const chartData = $derived.by(() => {
let yesCount = 0;
@@ -81,13 +82,8 @@
});
</script>
{#if chartData.length > 0}
<Chart.Container config={chartConfig} class="ui:h-[280px] ui:w-full ui:flex-col ui:items-center">
<Chart.PieChart data={chartData} key="key" label="label" value="responses" c="color" />
<Chart.ChartLegend items={chartData.map((d) => ({ label: d.label, color: d.color, value: d.responses }))} />
</Chart.Container>
{:else}
<p class="ui:text-muted-foreground ui:text-sm">
{getSubmissionLabel(labels, 'submission.chart.no_data', 'No responses yet')}
</p>
{/if}
<SubmissionResponsePieChart
{chartData}
{chartConfig}
emptyLabel={getSubmissionLabel(labels, 'submission.chart.no_data', 'No responses yet')}
/>