docs: add scroll-to-top spec and agent convention (#917)

Define a shared back-to-top control for overflowing course pages and require it in AGENTS.md so long-content surfaces reuse one component.

Co-authored-by: Cursor Agent <[email protected]>
This commit is contained in:
Rotimi Best
2026-08-16 22:49:40 +01:00
committed by GitHub
co-authored by Cursor Agent
parent 1ffdd4bbae
commit 4aaa03c81b
3 changed files with 265 additions and 0 deletions
+29
View File
@@ -499,6 +499,33 @@ Use `@cio/ui/base/page` for dashboard page shells. See `packages/ui/README.md`
/>
```
### Scroll to top
Any surface whose **main column can overflow the viewport** must use the shared `ScrollToTop` control. Do not invent a one-off back-to-top button, FAB, or “scroll up” link.
Full behavior, threshold, clearance, and mount points: `prd/scroll-to-top/README.md`.
**Rules:**
- Implement and import from `@cio/ui/custom/scroll-to-top` (see that spec if the file is not there yet — add the shared component, do not inline a button in the page).
- Hidden until the scroll container overflows **and** the user has scrolled at least **one viewport**. Hidden again below 0.8 viewports. Never visible at the top of the page.
- Click only scrolls that container to `top: 0`. Prefer `behavior: 'smooth'`; use instant when `prefers-reduced-motion` is set.
- Fixed to the **viewport**, bottom-right, icon-only `Button`/`IconButton` with `variant="secondary"`.
- Pass `label` from the host (`$t('common.scroll_to_top')` in the dashboard). The UI package does not own copy.
- Prefer **one mount per shell**, not per leaf page:
- Public lessons and exercises → `PublicCourse.PublicCourseShell` (`clearance="mobile-nav"`).
- Authenticated course contents list, lesson, and exercise → `routes/(app)/courses/[id]/+layout.svelte` (Ask AI clearance on lesson/exercise routes).
- New long-read pages (community thread, public course landing, long catalogs, etc.) must include the same component. Do not mount it on `(app)/+layout.svelte` or inside dialogs/sheets/nested panes.
```svelte
<script lang="ts">
import { ScrollToTop } from '@cio/ui/custom/scroll-to-top';
import { t } from '$lib/utils/functions/translations';
</script>
<ScrollToTop label={$t('common.scroll_to_top')} />
```
## Emails: system vs org-branded
Every transactional email in `packages/email/src/emails` is one of two kinds — decide deliberately, because it changes the branding, the schema, and the `from` address.
@@ -540,6 +567,7 @@ Links embedded in transactional emails must use the correct dashboard host. Temp
- Use `SvelteSet`/`SvelteMap` from `svelte/reactivity` for reactive collections (not `new Set`/`new Map`)
- Reset modal/form state in close/submit handlers or `onOpenChange`, not in `$effect` tied to a steady “closed” condition
- Mutate bound `$state` object fields in place when clearing forms (don't reassign the whole object)
- Use `ScrollToTop` (`@cio/ui/custom/scroll-to-top`) on any page whose main column can overflow the viewport (see **Scroll to top**)
### ❌ DON'T
- Put business logic in routes or queries
@@ -557,6 +585,7 @@ Links embedded in transactional emails must use the correct dashboard host. Temp
- **Use `$effect` to reset form/modal state whenever a boolean is false** — use `onOpenChange` or explicit handlers on close instead
- **Reassign whole bound state objects to clear forms** (e.g. `fields = {}`) — mutate properties in place
- **Use inline type imports** (e.g. `import('Package').Type` in type positions) — use top-level `import type` instead
- Build a one-off back-to-top button — use `ScrollToTop` (see **Scroll to top** and `prd/scroll-to-top/README.md`)
## Checklist for New Routes