fix(dashboard): lesson nav build error and add CI build check (#765)
* fix(dashboard): repair lesson nav build and improve locked feedback Replace stale $isOrgStudent reference with isCourseLearnerView to fix the Svelte build error. Disable prev/next when the adjacent item is locked, show lock tooltips, and surface a snackbar if navigation is blocked (including keyboard shortcuts). Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com> * ci: add package build check and AGENTS.md verification steps Document required dashboard/API build commands for agents and add a GitHub Actions workflow that runs those builds on PRs touching apps or packages. Copy dashboard .env.example in CI so vite build has required public env vars. Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com> --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>
This commit is contained in:
co-authored by
Rotimi Best
Cursor Agent
parent
3ef286ed39
commit
ca3fdd5b6a
@@ -0,0 +1,53 @@
|
||||
name: Package build check
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- 'apps/dashboard/**'
|
||||
- 'apps/api/**'
|
||||
- 'packages/**'
|
||||
- 'pnpm-lock.yaml'
|
||||
- 'package.json'
|
||||
- 'turbo.json'
|
||||
- '.nvmrc'
|
||||
- '.github/workflows/package-build-check.yml'
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- 'apps/dashboard/**'
|
||||
- 'apps/api/**'
|
||||
- 'packages/**'
|
||||
- 'pnpm-lock.yaml'
|
||||
- 'package.json'
|
||||
- 'turbo.json'
|
||||
- '.nvmrc'
|
||||
- '.github/workflows/package-build-check.yml'
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20'
|
||||
cache: 'pnpm'
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Prepare dashboard env
|
||||
run: cp apps/dashboard/.env.example apps/dashboard/.env
|
||||
|
||||
- name: Build dashboard
|
||||
run: pnpm --filter @cio/dashboard^... build && pnpm --filter @cio/dashboard build
|
||||
|
||||
- name: Build API
|
||||
run: pnpm --filter @cio/api^... build && pnpm --filter @cio/api build
|
||||
@@ -20,6 +20,29 @@ When a task requires factual information (API specifications, context window siz
|
||||
- Commit messages must follow the Conventional Commits 1.0.0 spec: https://www.conventionalcommits.org/en/v1.0.0/
|
||||
- Never include agent-provider attribution in commits or commit trailers. Do not add `Co-authored-by` lines for Cursor, Claude, Codex, or any other tool.
|
||||
|
||||
## Verification before commit/PR
|
||||
|
||||
Before committing or opening/updating a PR, run the build checks that match the areas you changed. Use Node 20 (the repo's `.nvmrc` version):
|
||||
|
||||
```bash
|
||||
export PATH="$HOME/.nvm/versions/node/v20.19.3/bin:$PATH"
|
||||
```
|
||||
|
||||
- **Dashboard changes** (`apps/dashboard/**`, or shared packages consumed by the dashboard):
|
||||
|
||||
```bash
|
||||
test -f apps/dashboard/.env || cp apps/dashboard/.env.example apps/dashboard/.env
|
||||
pnpm --filter @cio/dashboard^... build && pnpm --filter @cio/dashboard build
|
||||
```
|
||||
|
||||
- **API changes** (`apps/api/**`, or shared packages consumed by the API):
|
||||
|
||||
```bash
|
||||
pnpm --filter @cio/api^... build && pnpm --filter @cio/api build
|
||||
```
|
||||
|
||||
Also run `pnpm format:check` (see Translation, Formatting, and Git Workflow above). Do not commit if any verification step fails.
|
||||
|
||||
## Naming Convention
|
||||
|
||||
- Use kebab-case for files (e.g. `user-profile.svelte`, `org.svelte.ts`).
|
||||
|
||||
+32
-11
@@ -7,8 +7,10 @@
|
||||
import { Button } from '@cio/ui/base/button';
|
||||
import * as Tooltip from '@cio/ui/base/tooltip';
|
||||
import { PercentRingProgress } from '@cio/ui/custom/percent-ring-progress';
|
||||
import { get } from 'svelte/store';
|
||||
import { isCourseLearnerView } from '$lib/utils/store/app';
|
||||
import { getStudentContentLockReason } from '$features/ai-assistant/utils/content-ask-ai-bar';
|
||||
import { getStudentContentLockTitleKey } from '$features/course/utils/content-lock-utils';
|
||||
import { t } from '$lib/utils/functions/translations';
|
||||
import { courseApi, lessonApi } from '$features/course/api';
|
||||
import { getOrderedNavigableContent, getContentRoute } from '$features/course/utils/content';
|
||||
@@ -50,16 +52,21 @@
|
||||
};
|
||||
});
|
||||
|
||||
function getNavigableLockReason(target: CourseContentItem | null) {
|
||||
if (!target) return null;
|
||||
|
||||
const targetType = target.type === ContentType.Lesson ? ContentType.Lesson : ContentType.Exercise;
|
||||
return getStudentContentLockReason(courseApi.course, target.id, targetType);
|
||||
}
|
||||
|
||||
function goToContent(target: CourseContentItem | null) {
|
||||
if (!target) return;
|
||||
|
||||
if ($isCourseLearnerView) {
|
||||
const targetType = target.type === ContentType.Lesson ? ContentType.Lesson : ContentType.Exercise;
|
||||
const lockReason = getStudentContentLockReason(courseApi.course, target.id, targetType);
|
||||
const lockReason = getNavigableLockReason(target);
|
||||
|
||||
if (lockReason) {
|
||||
return;
|
||||
}
|
||||
if (get(isCourseLearnerView) && lockReason) {
|
||||
snackbar.error(getStudentContentLockTitleKey(lockReason));
|
||||
return;
|
||||
}
|
||||
|
||||
const courseIdResolved = courseApi.course?.id;
|
||||
@@ -71,6 +78,20 @@
|
||||
|
||||
const isPrevDisabled = $derived(!prevNextContent.prev);
|
||||
const isNextDisabled = $derived(!prevNextContent.next);
|
||||
const prevNavLockReason = $derived($isCourseLearnerView ? getNavigableLockReason(prevNextContent.prev) : null);
|
||||
const nextNavLockReason = $derived($isCourseLearnerView ? getNavigableLockReason(prevNextContent.next) : null);
|
||||
const isPrevNavBlocked = $derived(Boolean(prevNavLockReason));
|
||||
const isNextNavBlocked = $derived(Boolean(nextNavLockReason));
|
||||
const prevNavTooltip = $derived(
|
||||
prevNavLockReason
|
||||
? $t(getStudentContentLockTitleKey(prevNavLockReason))
|
||||
: $t('course.navItem.lessons.prev_shortcut')
|
||||
);
|
||||
const nextNavTooltip = $derived(
|
||||
nextNavLockReason
|
||||
? $t(getStudentContentLockTitleKey(nextNavLockReason))
|
||||
: $t('course.navItem.lessons.next_shortcut')
|
||||
);
|
||||
const isLessonComplete = $derived.by(() => {
|
||||
if (!lessonId) return false;
|
||||
const lesson = lessonItems.find((l) => l.id === lessonId);
|
||||
@@ -145,7 +166,7 @@
|
||||
updateCourseContentCompletion(currentLessonId, isComplete);
|
||||
|
||||
const allComplete =
|
||||
$isOrgStudent &&
|
||||
get(isCourseLearnerView) &&
|
||||
isComplete &&
|
||||
navigableContentItems.length > 0 &&
|
||||
navigableContentItems.every((item) => item.isComplete);
|
||||
@@ -231,14 +252,14 @@
|
||||
size="icon-sm"
|
||||
variant="outline"
|
||||
onclick={() => goToContent(prevNextContent.prev)}
|
||||
disabled={isPrevDisabled}
|
||||
disabled={isPrevDisabled || isPrevNavBlocked}
|
||||
aria-label={$t('course.navItem.lessons.prev')}
|
||||
>
|
||||
<ChevronLeftIcon size={14} />
|
||||
</Button>
|
||||
</Tooltip.Trigger>
|
||||
<Tooltip.Content side="bottom" sideOffset={4}>
|
||||
{$t('course.navItem.lessons.prev_shortcut')}
|
||||
{prevNavTooltip}
|
||||
</Tooltip.Content>
|
||||
</Tooltip.Root>
|
||||
|
||||
@@ -248,14 +269,14 @@
|
||||
size="icon-sm"
|
||||
variant="outline"
|
||||
onclick={() => goToContent(prevNextContent.next)}
|
||||
disabled={isNextDisabled}
|
||||
disabled={isNextDisabled || isNextNavBlocked}
|
||||
aria-label={$t('course.navItem.lessons.next')}
|
||||
>
|
||||
<ChevronRightIcon size={14} />
|
||||
</Button>
|
||||
</Tooltip.Trigger>
|
||||
<Tooltip.Content side="bottom" sideOffset={4}>
|
||||
{$t('course.navItem.lessons.next_shortcut')}
|
||||
{nextNavTooltip}
|
||||
</Tooltip.Content>
|
||||
</Tooltip.Root>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user