Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,37 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

## [1.1.5] - 2026-02-09

### Added

- Class-aware state foundations: `Classroom` model support, class utilities (`lib/classes.ts`), and class-focused storage/test coverage.
- Shared `ClassSelector` component for class selection across students, generator, play, breakout rooms, and projects.
- Unified quiz editor test coverage for form + import workflows (`components/quizzes/__tests__/quiz-editor-form.test.tsx`).
- Shared select wrapper tests for popup alignment defaults (`components/ui/__tests__/select.test.tsx`).

### Changed

- Student workflow expanded to class-first management: create/select classes, import full class rosters, and manage students in the active class.
- Generator, quiz play, breakout groups, and project lists now run with active-class scoping.
- Quiz editing/import flow consolidated into `QuizEditorForm`; legacy `quiz-import-card` removed.
- Quiz question list card behavior refined for better overflow/height handling.
- Shared `SelectContent` now defaults to popper-style content-fit behavior and supports explicit trigger-aligned positioning via `alignItemWithTrigger={true}` where needed.
- App version updated to `1.1.5` in `package.json`.
- Sidebar version label now reads version dynamically from `package.json` via server layout prop wiring.
- Dependency/version refresh from the `main..HEAD` baseline commits (including `package.json` and `bun.lock` updates from `e9d61c2`).

### Tests

- Expanded reducer/storage/type-guard coverage for class-aware persistence and migration paths.
- Added class utility tests (`lib/__tests__/classes.test.ts`) and updated student/reducer expectations.
- Added select alignment behavior tests in `components/ui/__tests__/select.test.tsx`.

### Documentation

- Updated README and project docs for class-scoped workflows and unified quiz editing/import.
- Updated component docs to describe select popup auto-sizing defaults and trigger-alignment override.

## [1.1.4] - 2026-02-05

### Changed
Expand Down
7 changes: 6 additions & 1 deletion app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import type { Metadata } from 'next';
import { Geist, Geist_Mono } from 'next/font/google';

import './globals.css';
import packageJson from '@/package.json';

import AppShell from '@/components/app-shell';
import Footer from '@/components/footer';
Expand Down Expand Up @@ -62,6 +63,8 @@ export default function RootLayout({
}: {
children: React.ReactNode;
}) {
const appVersion = packageJson.version;

return (
<html lang="en_GB" suppressHydrationWarning>
<head>
Expand Down Expand Up @@ -91,7 +94,9 @@ export default function RootLayout({
className={`${geistSans.variable} ${geistMono.variable} antialiased`}>
<ThemeProvider attribute="class" defaultTheme="dark">
<AppStoreProvider>
<AppShell footer={<Footer />}>{children}</AppShell>
<AppShell appVersion={appVersion} footer={<Footer />}>
{children}
</AppShell>
<PrivacyNotice />
<Toaster closeButton position="bottom-center" />
</AppStoreProvider>
Expand Down
5 changes: 4 additions & 1 deletion components/app-shell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,14 @@ import Header from './header';
/**
* Global app shell that renders the sidebar, header, and page content.
* Styled to match the design-6 command center aesthetic with phase-colored navigation.
* Provide `appVersion` from server layout so the sidebar version stays in sync with package metadata.
*/
export default function AppShell({
appVersion,
children,
footer,
}: {
appVersion: string;
children: React.ReactNode;
footer?: React.ReactNode;
}) {
Expand Down Expand Up @@ -86,7 +89,7 @@ export default function AppShell({
<div className="flex items-center justify-between gap-2 px-2 text-sm text-muted-foreground group-data-[collapsible=icon]:justify-center">
<span className="flex items-center gap-1.5 group-data-[collapsible=icon]:text-xs">
<SparklesIcon className="size-3 text-primary group-data-[collapsible=icon]:hidden" />
v1.1.4
v{appVersion}
</span>
<span className="text-xs font-semibold uppercase tracking-[0.15em] text-primary/60 group-data-[collapsible=icon]:hidden">
Classroom
Expand Down
7 changes: 6 additions & 1 deletion components/students/student-table.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,9 @@ export default function StudentTable({
(student) => student.id === editingStudentId,
) ?? null)
: null;
const selectedEditClass = editClassId
? (state.persisted.classes.find((entry) => entry.id === editClassId) ?? null)
: null;

/**
* Keeps the student list card height aligned to the form card on desktop widths.
Expand Down Expand Up @@ -414,7 +417,9 @@ export default function StudentTable({
if (editError) setEditError(null);
}}>
<SelectTrigger id="edit-student-class">
<SelectValue placeholder="Select class" />
<SelectValue placeholder="Select class">
{selectedEditClass?.name ?? 'Select class'}
</SelectValue>
</SelectTrigger>
<SelectContent>
{state.persisted.classes.map((entry) => (
Expand Down
59 changes: 59 additions & 0 deletions components/ui/__tests__/select.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { render, waitFor } from '@testing-library/react';
import { describe, expect, it } from 'vitest';

import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from '@/components/ui/select';

/**
* Renders an open select so popup-level behavior can be asserted in tests.
*
* @param alignItemWithTrigger Optional popup alignment override.
* @returns Promise resolving to the rendered popup element.
*/
async function renderOpenSelect(
alignItemWithTrigger?: boolean,
): Promise<HTMLElement> {
render(
<Select defaultOpen defaultValue="class-a">
<SelectTrigger aria-label="Class select">
<SelectValue placeholder="Select class" />
</SelectTrigger>
<SelectContent
{...(alignItemWithTrigger === undefined
? {}
: { alignItemWithTrigger })}>
<SelectItem value="class-a">Class A</SelectItem>
<SelectItem value="class-b">Class B</SelectItem>
</SelectContent>
</Select>,
);

let popup: HTMLElement | null = null;
await waitFor(() => {
popup = document.querySelector<HTMLElement>('[data-slot="select-content"]');
expect(popup).not.toBeNull();
});

if (!popup) {
throw new Error('Select popup did not render');
}

return popup;
}

describe('SelectContent', () => {
it('defaults to popper-style popup behavior', async () => {
const popup = await renderOpenSelect();
expect(popup).toHaveAttribute('data-align-trigger', 'false');
});

it('supports explicit trigger-aligned popup behavior', async () => {
const popup = await renderOpenSelect(true);
expect(popup).toHaveAttribute('data-align-trigger', 'true');
});
});
13 changes: 10 additions & 3 deletions components/ui/select.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -57,14 +57,21 @@ function SelectTrigger({
)
}

/**
* Renders the positioned select popup and option list.
* By default, the popup uses popper-style positioning so menu height tracks
* content naturally while still respecting the shared max-height cap.
* Callers can opt into trigger-aligned positioning with
* `alignItemWithTrigger={true}` when needed.
*/
function SelectContent({
className,
children,
side = "bottom",
sideOffset = 4,
align = "center",
alignOffset = 0,
alignItemWithTrigger = true,
alignItemWithTrigger = false,
...props
}: SelectPrimitive.Popup.Props &
Pick<
Expand All @@ -87,9 +94,9 @@ function SelectContent({
className={cn("bg-popover text-popover-foreground data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-open:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 ring-foreground/10 min-w-32 rounded-lg shadow-md ring-1 duration-100 data-[side=inline-start]:slide-in-from-right-2 data-[side=inline-end]:slide-in-from-left-2 relative isolate z-50 h-fit max-h-[min(18rem,var(--available-height))] w-(--anchor-width) origin-(--transform-origin) overflow-x-hidden overflow-y-auto data-[align-trigger=true]:animate-none", className )}
{...props}
>
<SelectScrollUpButton />
{alignItemWithTrigger ? <SelectScrollUpButton /> : null}
<SelectPrimitive.List>{children}</SelectPrimitive.List>
<SelectScrollDownButton />
{alignItemWithTrigger ? <SelectScrollDownButton /> : null}
</SelectPrimitive.Popup>
</SelectPrimitive.Positioner>
</SelectPrimitive.Portal>
Expand Down
3 changes: 2 additions & 1 deletion documentation/project-docs/components.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ Base components in `components/ui/` use Tailwind and (where noted) Base UI / sha
| `Button` | CVA variants; use `button-variants.ts` for server-safe variants. |
| `Card`, `CardHeader`, etc. | Layout primitives. |
| `Input`, `Textarea` | Form inputs. |
| `Select`, `Label`, `Field` | Form field wrappers. |
| `Select`, `Label`, `Field` | Form field wrappers. `SelectContent` defaults to popper-style content-fit behavior; pass `alignItemWithTrigger={true}` for trigger-aligned positioning. |
| `Dialog`, `DialogContent`, `DialogTrigger`, etc. | Modal dialogs (used by PageInfoDialog, etc.). |
| `Tabs`, `TabsList`, `TabsTrigger`, `TabsContent` | Tabbed content. |
| `AlertDialog` | Confirmation dialogs. |
Expand All @@ -124,6 +124,7 @@ import { buttonVariants } from '@/components/ui/button-variants';
Component tests:

- `StudentForm` – `components/students/__tests__/student-form.test.tsx`
- `Select` – `components/ui/__tests__/select.test.tsx`
- `QuizSelector` – `components/quizzes/__tests__/quiz-selector.test.tsx`
- `PageInfoDialog` – `components/utility/__tests__/page-info-dialog.test.tsx`

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "teacherbuddy",
"version": "1.1.4",
"version": "1.1.5",
"private": true,
"packageManager": "bun@1.3.4",
"scripts": {
Expand Down
Loading