Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
166 commits
Select commit Hold shift + click to select a range
50f6c8a
feat: save theme preference to local storage and apply theme on load
kake21 Apr 15, 2026
f612602
feat: update navbar items to include theme
kake21 Apr 15, 2026
34023eb
feat: svg versions of logos
kake21 Apr 15, 2026
a704774
feat: main landmark
kake21 Apr 16, 2026
460a49c
chore: remove old image system
JohanHjelsethStorstad Mar 24, 2026
2af8894
refactor: make image service into a subservice with implementations
JohanHjelsethStorstad Mar 24, 2026
4621dca
refactor: make the store into a factory that implements two functions
JohanHjelsethStorstad Apr 4, 2026
cf27133
chore: make image system actually delete files when a image is deleted
JohanHjelsethStorstad Apr 4, 2026
0b3cb24
chore: set up system for generating standard images from config
JohanHjelsethStorstad Apr 13, 2026
cb1acfb
fix: mount the dobbelomegamanifest volume
JohanHjelsethStorstad Apr 13, 2026
c15a275
chore: add config for dev_profile_images
JohanHjelsethStorstad Apr 13, 2026
9fd792e
chore: add config for all images in standard store
JohanHjelsethStorstad Apr 13, 2026
aa7a731
feat: read and regenerate standard images
JohanHjelsethStorstad Apr 13, 2026
35f3820
refactor: on StandardStoreFile make a method to easily convert to ima…
JohanHjelsethStorstad Apr 14, 2026
3a08498
refacotor: use the imageUploadData to seed dynamic images for cms
JohanHjelsethStorstad Apr 14, 2026
c02ece3
chore: remove som special cms images (work in progress)
JohanHjelsethStorstad Apr 16, 2026
ea12ed3
feat: more svgs
kake21 Apr 16, 2026
1130d3e
refactor: update background color for menu items to improve light mode
kake21 Apr 16, 2026
f90b7c4
accessibility: add descriptive aria-labels to Link components across …
kake21 Apr 21, 2026
4d74d26
Merge branch 'main' into feat/themesAgain
kake21 Apr 21, 2026
5e27670
Update src/app/_components/UI/ThemeEnabler.tsx
kake21 Apr 21, 2026
d2af6a2
Update src/app/admin/lockers/page.tsx
kake21 Apr 21, 2026
beb8805
Update src/app/users/[username]/(user-admin)/theme/theme.ts
kake21 Apr 21, 2026
46e5bab
feat: introduce page titles and improve layout structure across compo…
kake21 May 28, 2026
1d4d2af
refactor: standardize transition timing and enhance layout styles acr…
kake21 May 29, 2026
7f606a2
refactor: update to ohma color variables and grid layout for consiste…
kake21 May 29, 2026
bebd846
chore: add semantics to tell which images are available after seeding…
JohanHjelsethStorstad Jun 9, 2026
40544a2
chore: upsering
JohanHjelsethStorstad Jul 14, 2026
3af5e2e
chore: remove timeout comment by variable name
JohanHjelsethStorstad Jul 15, 2026
0c27ff7
refactor: omegaOrder service to use serviceOperations
JohanHjelsethStorstad Jul 17, 2026
f965c92
chore: accept NOT FOUND errors as non existance in upsert function
JohanHjelsethStorstad Jul 17, 2026
b69b726
chore: defineSeedOperation using context
JohanHjelsethStorstad Jul 17, 2026
0a2cdd7
refactor: seed of article categories and orders
JohanHjelsethStorstad Jul 17, 2026
8958a3d
chore: add types to extract Data, Params, ImplementationParams from s…
JohanHjelsethStorstad Jul 17, 2026
6c81eb6
docs: simplify doc strings for seedArticleCategories
JohanHjelsethStorstad Jul 18, 2026
66ee094
refactor: flairs to implement its own special image collection
JohanHjelsethStorstad Jul 18, 2026
edd7ce9
refactor: seed images to use defineSeedOperation
JohanHjelsethStorstad Jul 18, 2026
f769f18
fix: deleting images that do not exist
JohanHjelsethStorstad Jul 18, 2026
97f0820
refactor: seeding of news articles
JohanHjelsethStorstad Jul 19, 2026
74eadd2
chore: move out ArticleCategory and NewsArticle scemas from cms to be…
JohanHjelsethStorstad Jul 19, 2026
78b7dfc
chore: migrate seedDevImages to defineSeedOperation
JohanHjelsethStorstad Jul 19, 2026
f4c9860
refactor: seedDevNews
JohanHjelsethStorstad Jul 19, 2026
545cfd7
refactor: seedSpecialCms
JohanHjelsethStorstad Jul 19, 2026
dbf5dae
refactor: update committee service to implement its own special colle…
JohanHjelsethStorstad Jul 19, 2026
dc809b5
refactor: ombul to implement OMBUL COVERS special collection
JohanHjelsethStorstad Jul 19, 2026
8478746
chore: extra sanity check for ownership of images destroyed even thou…
JohanHjelsethStorstad Jul 19, 2026
35a5c66
feat: paragraphs for ombul
JohanHjelsethStorstad Jul 19, 2026
c420549
chore: enforce allowedExt in store.createFile is narrower than that o…
JohanHjelsethStorstad Jul 19, 2026
f116f23
fix: array buffer type in upload image
JohanHjelsethStorstad Jul 19, 2026
3674f69
refactor: user service to implement special PROFILE_IMAGES collection
JohanHjelsethStorstad Jul 20, 2026
9c7276b
chore: set up main landing page for image collections list
JohanHjelsethStorstad Jul 20, 2026
8850f60
chore: tie Cursor and details types to schemas
JohanHjelsethStorstad Jul 20, 2026
b09e0ab
chore: set up new route structure for images pages
JohanHjelsethStorstad Jul 20, 2026
d10e768
chore: use StandardImage for the removed CmsImage parts
JohanHjelsethStorstad Jul 20, 2026
6cbc3f9
fix: use new standard image implementation in fallback of CmsImage co…
JohanHjelsethStorstad Jul 20, 2026
88ae394
chore: rename dynamic image actions to make clear they are for dynami…
JohanHjelsethStorstad Jul 21, 2026
5695738
chore: assemble all panel operation in a new 'service' assebling all …
JohanHjelsethStorstad Jul 21, 2026
cefa465
feat: ClientData provider for easier client getting of data
JohanHjelsethStorstad Jul 22, 2026
1883b0b
chore: merge CmsImageClient and Cmsimage into one
JohanHjelsethStorstad Jul 22, 2026
677c535
chore: add TODO comment
JohanHjelsethStorstad Jul 23, 2026
a9b569f
chore: set up ImageCollectionList to use special collections
JohanHjelsethStorstad Jul 23, 2026
8e829eb
chore: show special collections in CollectionList page
JohanHjelsethStorstad Jul 23, 2026
cca7d66
chore: show special collections in CmsImageEditor
JohanHjelsethStorstad Jul 23, 2026
211e860
fix: prismaWhereFilter not applied to a level in readCollectionPage
JohanHjelsethStorstad Jul 23, 2026
fe2ffb0
chore: validate that a user administrates the collection of the image…
JohanHjelsethStorstad Jul 23, 2026
4702d9c
chore: add note to user that exposing an image through a cmsImage mig…
JohanHjelsethStorstad Jul 23, 2026
d7360fa
chore: set up for refactoring ImageList
JohanHjelsethStorstad Jul 23, 2026
c3539be
refactor: CollectionCard does not enforce being a link - new Collecti…
JohanHjelsethStorstad Jul 23, 2026
7304c0e
chore: remove the details from image pager. Should be bound seperatly
JohanHjelsethStorstad Jul 23, 2026
21585fc
refactor: ImageList -> ImageDisplay managing displaying of images for…
JohanHjelsethStorstad Jul 23, 2026
b9fbfa8
fix: old refs do refactored systems
JohanHjelsethStorstad Jul 23, 2026
db840c0
fix: seedDevUsers to use standard store
JohanHjelsethStorstad Jul 23, 2026
dadce7a
fix: career and education pages use current standard image action
JohanHjelsethStorstad Jul 24, 2026
5355dfa
fix: event registration reads default profile image through current s…
JohanHjelsethStorstad Jul 24, 2026
1fbffb0
fix: seedDevJobAds looks up the FAIR image by standardImage
JohanHjelsethStorstad Jul 24, 2026
6355d1d
chore: .bind -> configureAction in eduaction
JohanHjelsethStorstad Jul 24, 2026
de02747
chore: StandardImageServer rename and consistent use
JohanHjelsethStorstad Jul 24, 2026
e4b6b82
chore: make migrations use new image system
JohanHjelsethStorstad Jul 24, 2026
1ef3667
fix: circular dependencies fixed by exporting implementation of speci…
JohanHjelsethStorstad Jul 24, 2026
40d5ed4
fix: standard images race condition on seeding
JohanHjelsethStorstad Jul 24, 2026
e73e23b
chore: remove 'special' fields from data in operations. The implement…
JohanHjelsethStorstad Jul 24, 2026
8447f16
chore: do not log expected not founds in seeder
JohanHjelsethStorstad Jul 24, 2026
b395f42
fix: two long name in autogenerated special articles
JohanHjelsethStorstad Jul 24, 2026
610b6df
chore: seperate function for regeneration of special cms
JohanHjelsethStorstad Jul 24, 2026
f6218fa
chore: Big speedup in image uploader
JohanHjelsethStorstad Jul 26, 2026
4bdf8ca
fix: turbopack issue with importing directory of standardFiles
JohanHjelsethStorstad Jul 26, 2026
626e450
chore: wrong standard images used on frontpage
JohanHjelsethStorstad Jul 26, 2026
9275ae9
chore: first draft of visibility admin
JohanHjelsethStorstad Jul 27, 2026
e42bcf6
chore: wrong standard images on not-found error and loader
JohanHjelsethStorstad Jul 27, 2026
8b6e387
chore: feed visibility from server to collectionAdmin
JohanHjelsethStorstad Jul 27, 2026
3c16ac9
fix: run all authorizers needed for collectionAdmin not just trus one
JohanHjelsethStorstad Jul 27, 2026
9fd7854
fix: collection cards collapse in cms image editor
JohanHjelsethStorstad Jul 27, 2026
59f157d
fix: invisible buttons on edit article
JohanHjelsethStorstad Jul 27, 2026
574c806
chore: make ImageUploader just a form to increase modularity
JohanHjelsethStorstad Jul 27, 2026
6c0b95b
feat: enforce that admin visibility is a sub visibility of regular level
JohanHjelsethStorstad Jul 27, 2026
b85ae33
chore: reuse the util to get group orders
JohanHjelsethStorstad Jul 27, 2026
787db73
chore: make description of of visibility a component
JohanHjelsethStorstad Jul 27, 2026
253de22
chore: tests for dynamic images and visibility
JohanHjelsethStorstad Jul 27, 2026
ccdff0f
style: auto fix lining
JohanHjelsethStorstad Jul 27, 2026
8a72d8c
chore: fix seeder for ci in test
JohanHjelsethStorstad Jul 27, 2026
445e699
fix: bump timeouts for seeding in test
JohanHjelsethStorstad Jul 27, 2026
0d5b8b4
refactor: schools service
JohanHjelsethStorstad Jul 22, 2026
1fe754e
chore: remove old deprecated stuff from the small courses service
JohanHjelsethStorstad Jul 22, 2026
7226047
refactor: omegaId service
JohanHjelsethStorstad Jul 22, 2026
09a975c
Merge branch 'refactor/omegaId-service' into feat/image-svgs-and-asyn…
JohanHjelsethStorstad Jul 27, 2026
8b55692
refactor: rename base16 color tokens to semantic names, fix eslint er…
kake21 Jul 28, 2026
e082707
refactor: turn $gap into a runtime CSS custom property
kake21 Jul 28, 2026
d8bcc26
refactor: change --rounding from 16px to 1rem
kake21 Jul 28, 2026
783e363
style: interpolate SCSS variables inside calc()
kake21 Jul 28, 2026
f9421f2
style: redesign admin sidebar as nested cards, tune colors
kake21 Jul 28, 2026
d0ee792
style: cap page width at a 16:9 slice of viewport height
kake21 Jul 28, 2026
cc8ad9b
refactor: update background gradient
kake21 Jul 28, 2026
75ec3a4
fix: make loading.tsx respect the page layout
kake21 Jul 28, 2026
7e2d5fa
style: redesign CommitteeCard as a proper card
kake21 Jul 28, 2026
9d79c3a
style: redesign HeaderItemPopUp button and layout
kake21 Jul 28, 2026
26ba3a6
refactor: refine PopUp component styling and improve consistency
kake21 Jul 28, 2026
929669b
feat: implement visibility in news service
JohanHjelsethStorstad Jul 28, 2026
71142f3
style: switch buttons and tables to gap-based sizing with card rounding
kake21 Jul 28, 2026
9b865d3
feat: replace flair rank buttons with animated drag-to-reorder
kake21 Jul 28, 2026
00edd5a
chore: show visibility admin on frontend
JohanHjelsethStorstad Jul 29, 2026
c982061
fix: sypescript infer chain too deep
JohanHjelsethStorstad Jul 29, 2026
ed414ae
style: bring TextInput, DateInput, Checkbox and Textarea to the new s…
kake21 Jul 29, 2026
dbf3fd1
fix: reclaim dead subPageNavBar row in mobile layout grid
kake21 Jul 29, 2026
73bc06c
chore: remove unused dependencies from package-lock.json
kake21 Jul 29, 2026
203054c
style: color user cards and profile page borders by flair, fix sub-na…
kake21 Jul 29, 2026
e065f87
refactor: remove unused neumorphism mixin
kake21 Jul 29, 2026
168a578
refactor: consolidate color tokens onto the theme system
kake21 Jul 29, 2026
97d60f9
feat: add component test page, popup-style confirmations, fix seconda…
kake21 Jul 29, 2026
59d94c3
style: flatten ohma.card, redesign /admin/dots, add raised-background…
kake21 Jul 29, 2026
427c4ec
feat: add custom Dropdown/SearchableDropdown components, sortable/sta…
kake21 Jul 29, 2026
9e4c8a9
style: zone jobads like events, fix omegaquote bubble tail
kake21 Aug 3, 2026
dc2e86e
fix: edit-mode icon visibility/positioning, frontpage preview link, m…
kake21 Aug 3, 2026
6160f0a
fix: avoid NavTooltip hydration mismatch by deferring Radix wrapper t…
kake21 Aug 3, 2026
5eaa15b
fix: hide DesktopSideBar admin panel entirely when there's nothing to…
kake21 Aug 3, 2026
13a3ada
feat: add Omega-ID/Konto buttons to profile page, standardize on Button
kake21 Aug 3, 2026
36993e7
fix: floating sub-nav on user-admin pages, let each subpage own its c…
kake21 Aug 5, 2026
7651e5b
style: give each theme its own row with a live app-chrome preview
kake21 Aug 5, 2026
d146a45
fix: resolve eslint errors (set-state-in-effect, max-len)
kake21 Aug 5, 2026
0aa33aa
Merge branch 'main' into feat/islands
kake21 Aug 5, 2026
8206ea6
fix: make footer wrap flexibly instead of squeezing into fixed grid c…
kake21 Aug 5, 2026
253593e
style: customize scrollbar with invisible track and visible thumb
kake21 Aug 5, 2026
4caa4c5
Merge branch 'feat/news-auth' into feat/islands
kake21 Aug 7, 2026
7c9a5f5
style: adjust ReportButton and NavBar styles for consistent sizing an…
kake21 Aug 9, 2026
ff70d20
feat: add expandable navigation to DesktopSideBar and update related …
kake21 Aug 9, 2026
a688333
fix: remove UserNavigation popup in favor of direct profile link
kake21 Aug 9, 2026
5610b39
fix: expand navbar logo's clickable area to the full button
kake21 Aug 9, 2026
a9a90e4
fix: overhaul image-collections page styling and layout
kake21 Aug 10, 2026
17b6380
lint: warn on raw <input> elements outside the UI component folder
kake21 Aug 10, 2026
192a212
fix: crop the CMS image picker preview instead of letterboxing it
kake21 Aug 10, 2026
4a13035
fix: stop images overflowing their cards, make width overridable with…
kake21 Aug 10, 2026
1771dcf
fix: stop useEditMode's registration effect re-running on every render
kake21 Aug 10, 2026
f0e62fa
refactor: consolidate and streamline frontpage component styles and s…
kake21 Aug 10, 2026
5fa8288
fix: size the frontpage hero to the visible content area, not the raw…
kake21 Aug 10, 2026
73aa772
feat: integrate profile picture into navbar and enhance login/logout UX
kake21 Aug 13, 2026
5baaeca
feat: redesign auth layout as a split panel with hero image
kake21 Aug 18, 2026
0166c7e
style: remove redundant `flex-basis` rule from mobile layout in auth …
kake21 Aug 18, 2026
5040f03
style: set max-width for layout container to 2560px
kake21 Aug 18, 2026
0d56daf
chore(design-sync): add claude.ai/design sync inputs for the Ohma des…
kake21 Aug 18, 2026
8668515
style(ui): bring FileInput, Select and ColorInput onto the shared fie…
kake21 Aug 18, 2026
7d7c249
refactor(ui): retire the outline button tier
kake21 Aug 18, 2026
a4abf64
feat(events): render events as rows instead of cards
kake21 Aug 18, 2026
8ba7b2b
feat(jobads): render job ads as rows, sharing the event row shell
kake21 Aug 18, 2026
a29b7a2
feat(frontpage): lay the logged-in page out as islands
kake21 Aug 18, 2026
32c4151
fix(frontpage): stack paired islands and lay the row date out for phones
kake21 Aug 19, 2026
09d57c4
docs(design-sync): re-sync notes after the field and button rework
kake21 Aug 19, 2026
37181f1
fix(lint): ignore generated output and clean up the design-sync previews
kake21 Aug 19, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
177 changes: 177 additions & 0 deletions .design-sync/NOTES.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
# design-sync notes — projectNext → claude.ai/design

Repo-specific gotchas for future syncs. Read this **and** `config.json` before
re-running. Target project: `Ohma Design System`
(`451c0873-159d-403e-af6c-346418a5e5f9`).

## Why this repo needs a staging step

projectNext is a **Next.js app, not a component library**: no library build, no
`dist/`, and every component styles itself with `*.module.scss`, which esbuild
cannot load. `.design-sync/stage-pkg.mjs` mechanically transforms the real source
into a bundlable package under `.design-sync/.cache/pkg/`:

- compiles each `*.module.scss` → `*.module.css` with `sass` (a custom importer
resolves the `@/styles` alias); esbuild then handles `*.module.css` natively as
CSS modules, so the class-name map survives
- rewrites only import specifiers; component bodies are copied verbatim
- swaps `next/link` → `src/_shims/Link`, `next/navigation` → `src/_shims/navigation`
- emits `index.ts` (the barrel that defines the synced surface), `tsconfig.json`,
`package.json`, and `styles/globals.css`
- copies `public/fonts/**` in and rewrites the app's absolute `url('/fonts/…')`
references to relative ones

`.design-sync/scope.json` is the **source of truth for which components sync**
(`{Name: {group, src, export}}`). Add or remove entries there, not in config.

Rebuild command (also in `config.buildCmd`): `node .design-sync/stage-pkg.mjs`.
It emits the `.d.ts` tree itself — it wipes its output dir (`types/` included)
and the converter derives the component list from that tree, so a separate
forgotten `tsc` silently produced a 12-component bundle instead of the full set.

## Environment

- **Node is not on `PATH`** on this NixOS host. Use
`export PATH=/nix/store/hwjfj8m2kcsl7kz2xa5yf84jbfh9jssf-nodejs-24.18.1/bin:$PATH`
(or whatever `ls -d /nix/store/*nodejs*/bin` shows). The app itself normally runs
in Docker (`pn-dev`), but the sync runs on the host against `./node_modules`.
- **Playwright chromium is not downloaded.** The system chromium works:
`export DS_CHROMIUM_PATH=/run/current-system/sw/bin/chromium`. Only the
`playwright` npm package is installed in `.ds-sync/`
(`PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1`).
- Run the converter with `--node-modules ./node_modules --entry
./.design-sync/.cache/pkg/index.ts`.

## Decisions taken (and why)

- **`Date` → exported as `DateDisplay`.** The component's real name is `Date`, but
the converter's `.jsx` stub does `Object.assign(window, { <Name>: … })`, which
would clobber the global `Date` constructor on any page that loads it. Renamed in
the barrel only; the source is untouched.
- **`Inter` is vendored from Google Fonts** (`.design-sync/vendor-inter.mjs`, SIL
OFL). `src/styles/_fonts.scss` sets `$primary: 'Inter', sans-serif` — Inter is the
body font for the whole site — but the app never `@font-face`s it and ships no
file, so **production has been falling back to system sans**. Worth fixing in the
app itself. Cache is gitignored, so a fresh clone must re-run the script (needs
network).
- **`OhmaPreviewSurface`** exists only as the outer half of `cfg.provider`. The
card harness hard-codes `body{background:#fff}`, and this DS is dark-only with
near-white `--text`, so without it text and icons render invisible. It never
wraps a real design (those get the dark surface from `styles.css`).
- **`OhmaProviders`** nests the app's real `EditModeProvider`,
`PageTitleProvider` and `PopUpProvider`. Not a reimplementation — it imports them.
- **`CheckboxFieldPresent` is excluded from the card list** (`componentSrcMap:
null`). It renders a deliberately hidden input — internal form plumbing used by
`Checkbox`/`Slider`, never composed directly. Still in the bundle.
- **`MobileNavBar` is not synced**: it transitively imports `StandardImageServer`,
which pulls in the service layer.
- **`TagHeasderItemPopUp` keeps its typo** — that is the real export name in
`HeaderItems/HeaderItemPopUp.tsx`. Renaming would desync the DS from the app.

## Themes

The app ships **four** themes in
`src/app/users/[username]/(user-admin)/theme/theme.ts` — `Standard`, `Light`,
`Solarized`, `StjerneInnbygger` — applied at runtime by `applyTheme()`, which
sets 17 custom properties on `document.documentElement`. `ThemeEnabler` (mounted
in `app/layout.tsx`) restores the persisted choice on load.

`stage-pkg.mjs` reads `theme.ts` at build time (in a child process with
`--experimental-transform-types`, because the file uses an `enum` and Node's
default type-stripping can't handle that) and emits a
`:root[data-theme="<Name>"]` block per theme into `styles/globals.css`. The
barrel also re-exports `themes` / `applyTheme` / `subscribeToTheme` /
`getActiveTheme` / `ThemeName`, so a design can switch themes exactly as the app
does. `ThemeName` is excluded from the card list (`componentSrcMap: null`) — it's
an enum, not a component.

**Do not hand-copy palette values anywhere.** `theme.ts` is the single source of
truth; the CSS is generated from it on every build.

Verified in a real card (both mechanisms): `[data-theme]` and `applyTheme()` both
re-colour the shipped components.

**Inconsistency worth fixing in the app:** `globals.scss` and `theme.ts`'s
`Standard` disagree — `globals.scss` has `--text: hsl(0,0%,90%)` and
`--accent-blue: #037FFC`, while `themes.Standard` has `hsl(0,0%,80%)` and
`hsl(210,70%,50%)`. So picking "Standard" in the theme switcher yields slightly
different colours than a fresh page load, which never applied a theme.

## Component defects found while authoring previews

These are faithfully reproduced in the cards and documented in `conventions.md`.
They are **app bugs, not sync bugs** — fix them in the repo, then re-sync.

- ~~`UI/Select.module.scss` styles almost nothing…~~ **Fixed.** Select now uses
`appearance: none` plus the shared field treatment; verified in the built CSS
(`.Select_field` carries `surface-raised` / `ink-strong` / border / radius).
- ~~`UI/FileInput.module.scss` `.black` … dark text on black.~~ **Fixed.**
`.FileInput_black > .trigger .value` now resolves to `--ink-strong`.
- `UI/Slider.module.scss` `secondary` maps the track to `--surface-base`, invisible
against the app surface. Same for `TextInput`'s `secondary` text colour.
- `UI/Checkbox.module.scss`: the `children` branch (`.inputAndChildren`) drops the
custom box styling and falls back to the native checkbox.
- ~~`UI/BorderButton.module.scss` has no `:disabled` rule.~~ Retired — the
outline button tier was removed entirely (component, `borderBtn` mixin and all
ten stylesheet call sites) in favour of the filled `secondaryBtn` tier. If a
future sync sees `BorderButton` reappear in `scope.json`, that is a mistake.
- `Table/SimpleTable.tsx` wraps each `<tr>` in a `<Link>` when `links` is passed —
an `<a>` containing a `<tr>` is invalid HTML and browsers hoist it out, visibly
breaking the rows. The `WithLinks` story was dropped for this reason.
- `SideBarNavItem` / `AdminNav` labels only appear under
`.DesktopSideBar[data-expanded='true']`. That shell is `DesktopSideBar.tsx`, a
server component that can't ship, so `expanded` has no visible effect in a card.

## Known render warns

None. The final `package-validate.mjs` run exits 0 with **zero** warnings and
39/39 previews rendering cleanly (40 before `BorderButton` was retired). Any warn
on a future run is new — investigate before recording it here.

## Preview techniques used

- Overlay/menu open states are produced by **clicking the real trigger on mount**
(`useEffect` → `querySelector('button').click()`), never by hand-writing panel
markup. `SearchableDropdown` focuses its combobox input instead.
- Popup components carry `cfg.overrides.<Name> = {cardMode: "single",
primaryStory: "Opened", viewport: "900x620"}` because the panel is
position-fixed and escapes a grid cell.
- `SimpleTable` and `RadioLarge` use `cardMode: "column"` (they overflowed a
multi-column grid cell).
- `Dropzone` builds real decodable PNGs with a `<canvas>` at mount, because it
renders `URL.createObjectURL(file)` thumbnails. A truncated/padded PNG shows as
a broken-image box.
- `NavBarTitle`'s title state is produced by mounting a hidden real `PageWrapper`,
which is the only thing that writes `PageTitleContext`.
- Content is real Norwegian Omega material (committee names from
`NavBar/navDef.ts`, realistic event names) — never `foo`/`bar`. **Person names
are placeholders** (`Ola Nordmann` / `olanord`, plus invented Norwegian names);
don't put real members into previews — these cards are published.
- `<Name>.prompt.md` embeds examples lifted from `.design-sync/previews/<Name>.tsx`,
so editing a preview changes the uploaded doc too, not just the card.

## Re-sync risks

- **Node/chromium paths above are host-specific** and will rot on a nix-store
garbage-collect or an OS upgrade. Re-derive them, don't trust them.
- **`scope.json` drifts silently.** If a component listed there is renamed, moved
or gains a service import, `stage-pkg.mjs` fails loudly (good) — but a *new*
presentational component will simply never be synced until someone adds it.
Re-check `src/app/_components/` against `scope.json` on each sync.
- **Inter is fetched from Google Fonts at stage time.** No network → no Inter →
every card silently falls back to system sans. `stage-pkg.mjs` prints
`! Inter not vendored yet`; don't ignore it.
- **The scss `@font-face` strip** in `stage-pkg.mjs` removes `@font-face` blocks
from every `*.module.css` (sass re-emits them into each file via
`@use "ohma"`). If font handling in `src/styles/` changes shape, verify the
canonical copy still reaches `fonts/fonts.css`.
- **Component defects above may get fixed in the app.** When they are, update
`conventions.md`'s "Known rough edges" and re-author the affected previews
(`Select*` especially — they exist only to document a defect right now).
- Previews were verified against **Chromium 151**; native control rendering
(`<select>`, `<input type="date">`, `type="color"`) is UA-dependent.
- `_ds_bundle.js` was confirmed **byte-deterministic** across two consecutive
builds from an unchanged staged package. If a future no-change run reports a
changed `bundleSha12`, something in `stage-pkg.mjs`'s emission order has become
input-dependent — chase it rather than shrugging, because it would invalidate
the carried-forward grades that make re-syncs cheap.
45 changes: 45 additions & 0 deletions .design-sync/config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
{
"projectId": "451c0873-159d-403e-af6c-346418a5e5f9",
"shape": "package",
"pkg": "@ohma/ui",
"globalName": "OhmaUI",
"srcDir": "src",
"buildCmd": "node .design-sync/stage-pkg.mjs",
"readmeHeader": ".design-sync/conventions.md",
"cssEntry": "styles/globals.css",
"extraFonts": ["styles/globals.css"],
"provider": {
"component": "OhmaPreviewSurface",
"inner": { "component": "OhmaProviders" }
},
"overrides": {
"PopUp": { "cardMode": "single", "primaryStory": "Opened", "viewport": "900x620" },
"AddHeaderItemPopUp": { "cardMode": "single", "primaryStory": "Opened", "viewport": "900x620" },
"HelpHeaderItemPopUp": { "cardMode": "single", "primaryStory": "Opened", "viewport": "900x620" },
"SettingsHeaderItemPopUp": { "cardMode": "single", "primaryStory": "Opened", "viewport": "900x620" },
"UsersHeaderItemPopUp": { "cardMode": "single", "primaryStory": "Opened", "viewport": "900x620" },
"TagHeasderItemPopUp": { "cardMode": "single", "primaryStory": "Opened", "viewport": "900x620" },
"ArchiveHeaderItemPopUp": { "cardMode": "single", "primaryStory": "Opened", "viewport": "900x620" },
"Menu": { "cardMode": "single", "primaryStory": "Opened", "viewport": "900x620" },
"SimpleTable": { "cardMode": "column" },
"RadioLarge": { "cardMode": "column" }
},
"componentSrcMap": {
"OhmaProviders": null,
"OhmaPreviewSurface": null,
"CheckboxFieldPresent": null,
"ThemeName": null,
"SelectString": "src/inputs/Select.tsx",
"SelectNumber": "src/inputs/Select.tsx",
"DateDisplay": "src/data-display/Date.tsx",
"NavBarItem": "src/navigation/Item.tsx",
"SubPageNavBar": "src/navigation/SubPageNavBar.tsx",
"SubPageNavBarItem": "src/navigation/SubPageNavBar.tsx",
"AddHeaderItemPopUp": "src/overlays/HeaderItemPopUp.tsx",
"HelpHeaderItemPopUp": "src/overlays/HeaderItemPopUp.tsx",
"SettingsHeaderItemPopUp": "src/overlays/HeaderItemPopUp.tsx",
"UsersHeaderItemPopUp": "src/overlays/HeaderItemPopUp.tsx",
"TagHeasderItemPopUp": "src/overlays/HeaderItemPopUp.tsx",
"ArchiveHeaderItemPopUp": "src/overlays/HeaderItemPopUp.tsx"
}
}
136 changes: 136 additions & 0 deletions .design-sync/conventions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
# Ohma — how to build with this design system

Ohma is the component library of **Sanctus Omega Broderskab** (projectNext). It
is **fully themed** — four palettes, swapped at runtime — and the UI language is
Norwegian (bokmål). Write labels, placeholders and helper copy in Norwegian.

## Wrap every tree in `OhmaProviders`

Several components read app-level React context. Without the provider they throw
or render an empty placeholder:

- `PopUp` and every `*HeaderItemPopUp` throw `Pop up context needed for popups`
- `PageWrapper` writes the page title; `NavBarTitle` reads it — outside the
provider `NavBarTitle` renders only a blank reserved-space strip
- `AdminNav` reads edit-mode state

```jsx
const { OhmaProviders, PageWrapper, Button } = window.OhmaUI

<OhmaProviders>
<PageWrapper title="Arrangementer">
<Button color="primary">Lagre</Button>
</PageWrapper>
</OhmaProviders>
```

## Styling idiom: CSS custom properties, not utility classes

**There is no utility-class vocabulary — do not invent one, and do not write
`class="bg-surface-1"`-style names; nothing will resolve.** Component internals
are styled by hashed CSS-module classes you cannot target. Style your own layout
glue with the design tokens below, via inline `style` or your own CSS.

The tokens are declared on `:root`/`html` by the shipped stylesheet, and **17 of
the 21 are re-declared by every theme** (see Themes below). These 21 are the
complete set:

| Group | Tokens |
|---|---|
| Surfaces | `--surface-base` (page), `--surface-raised` (cards/panels), `--surface-hover`, `--surface-subtle` |
| Text | `--text`, `--text-muted`, `--text-inv` (on accent fills) |
| Ink | `--ink-hover`, `--ink-strong` |
| Accents | `--accent-blue` (primary), `--accent-red`, `--accent-green`, `--accent-yellow`, `--accent-orange`, `--accent-cyan`, `--accent-magenta`, `--accent-violet` |
| Shape / spacing | `--rounding` (1rem), `--gap` (0.5rem) |
| Depth | `--layer`, `--boxShadow` |

Idiomatic panel:

```jsx
<div style={{
background: 'var(--surface-raised)',
color: 'var(--text)',
padding: 'calc(2 * var(--gap))',
borderRadius: 'var(--rounding)',
}}>…</div>
```

**Always paint a `--surface-base` (or `--surface-raised`) background behind your
content**, and always take text colour from `--text` / `--text-muted`. Dropping
content onto an unpainted background is the single most common way to end up with
invisible text here, because the default theme's `--text` is near-white.

## Themes

Four themes ship with the design system: **`Standard`** (dark, the default),
**`Light`**, **`Solarized`** (warm light), and **`StjerneInnbygger`** (deep blue).
Each re-declares the same 17 colour tokens — every surface, text, ink and accent
above; `--rounding`, `--gap`, `--text-inv` and `--boxShadow` stay fixed.

Two equivalent ways to select one:

```jsx
// Declarative — the stylesheet ships a block per theme.
<html data-theme="Light">

// Runtime, exactly as the app's own theme picker does it:
const { applyTheme, ThemeName, themes } = window.OhmaUI
applyTheme(ThemeName.Solarized) // sets the custom properties on <html>
```

`themes` is the raw palette map (`themes.Light['surface-base']` etc.) if you need
to read a value rather than apply one. `subscribeToTheme(fn)` notifies on change;
`getActiveTheme()` reads the persisted choice.

**This is why the token rule matters.** A component or layout styled with
`var(--surface-raised)` follows all four themes for free. A hardcoded `#1a1a1e`
looks correct in `Standard` and broken in `Light` and `Solarized` — so never
hardcode a colour that a token already names.

## Type

`Inter` (300/400/500/700) is the body face and applies by default. `PlayfairDisplay`
(serif) is the display face — `PageWrapper` already uses it for its `h1`; reach for
it only for headings. Both ship with the bundle. Font sizes are fluid `clamp()`
values baked into the components; don't hardcode px type.

## Component groups

`actions` (Button) · `inputs` (TextInput, Textarea, NumberInput,
DateInput, ColorInput, FileInput, Checkbox, RadioLarge, Slider, Dropzone,
SelectString, SelectNumber, SearchableDropdown) · `overlays` (PopUp, Dropdown,
EditOverlay, AddHeaderItemPopUp, HelpHeaderItemPopUp, SettingsHeaderItemPopUp,
UsersHeaderItemPopUp, TagHeasderItemPopUp, ArchiveHeaderItemPopUp) · `feedback`
(ProgressBar, SlideInOnView) · `data-display` (SimpleTable, DateDisplay,
CountDown) · `layout` (PageWrapper, SocialIcons) · `navigation` (NavBarItem, Menu,
SideBarNavItem, AdminNav, NavBarTitle, NavTooltip, SubPageNavBar,
SubPageNavBarItem, ReportButton).

Read each component's `<Name>.d.ts` for its exact props and `<Name>.prompt.md`
for usage before composing it. The stylesheet of record is `styles.css` and its
imports (`fonts/fonts.css`, `_ds_bundle.css`).

## Conventions worth copying

- **Form controls take `name`** and render their own `<label>` from `label` — no
separate label element.
- **Every field is the same 56px shell** — `TextInput`, `Textarea`,
`NumberInput`, `DateInput`, `SelectString`, `SelectNumber`, `ColorInput`,
`FileInput`, `Dropdown`, `SearchableDropdown` — with a label that floats above
the value. Pass `background="raised"` on any of them when the field sits on
`--surface-raised`, so it stays distinguishable.
- **There is no outline/ghost button.** Every action is a filled `Button`:
`primary` for the main action on a view, `secondary` for everything beside it.
Don't invent a bordered variant — the design system deliberately has two
action tiers, both filled.
- `Button`'s `color` is `primary | secondary | green | red`; `green` is confirm,
`red` is destructive.
- Composition beats new markup: `SubPageNavBarItem` only lays out inside
`SubPageNavBar`; header-item popups belong in `PageWrapper`'s `headerItem`.

## Known rough edges (don't imitate, don't "fix" in a design)

- `Slider`'s `secondary` variant is invisible on the app surface; use `primary`,
`red` or `white`.
- `SimpleTable`'s `links` prop wraps `<tr>` in an `<a>`, which browsers hoist out
of the table — avoid it.
34 changes: 34 additions & 0 deletions .design-sync/previews/AddHeaderItemPopUp.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { AddHeaderItemPopUp, Button, TextInput } from '@ohma/ui'
import { useEffect, useRef } from 'react'
import type { ReactNode } from 'react'

/** The panel only opens from a real press on the trigger, so drive that on mount. */
function OpenOnMount({ children }: { children: ReactNode }) {
const ref = useRef<HTMLDivElement>(null)
useEffect(() => {
ref.current?.querySelector<HTMLButtonElement>('button')?.click()
}, [])
return <div ref={ref}>{children}</div>
}

const panel = (
<div style={{ display: 'grid', gap: '1rem', minWidth: '17rem' }}>
<h2 style={{ margin: 0 }}>Nytt arrangement</h2>
<TextInput name="eventName" label="Navn" background="raised" />
<Button color="primary">Opprett</Button>
</div>
)

export const Trigger = () => (
<AddHeaderItemPopUp popUpKey="add-trigger">{panel}</AddHeaderItemPopUp>
)

export const Opened = () => (
<OpenOnMount>
<AddHeaderItemPopUp popUpKey="add-opened">{panel}</AddHeaderItemPopUp>
</OpenOnMount>
)

export const CustomLabel = () => (
<AddHeaderItemPopUp popUpKey="add-label" label="Ny">{panel}</AddHeaderItemPopUp>
)
Loading
Loading