Hoppa till innehållet

Devportalen /portal/dev

Startsideportalens ekosystem för människa och agent

Agentvy

Modulkontraktet

ModuleManifest fält för fält, sloten de fyller och hur en modul registreras.

page manifest · /dev/moduler
id: moduler
path: /dev/moduler
titel: Modulkontraktet
summering: ModuleManifest fält för fält, sloten de fyller och hur en modul registreras.
nyckelord: modul, manifest, slots, widget, action, routes, intents, registry

## Manifestet
Kontraktet mellan skalet och modulen. Ett nytt fält gör kontraktet tyngre för alla moduler – nya fält ska bäras av minst två moduler innan de blir permanenta. Vill du se kontraktet i bruk i stället för i sammandrag: packages/modules/arenden är den kanoniska förlagan, utpekad på /dev/recept.

packages/module-sdk/src/types.ts:
```
export interface ModuleManifest {
  id: string;                    // "ledighet"
  name: string;                  // "Ledighetsansökningar"
  roles: Role[];                 // ["medarbetare"] | ["chef"] | båda
  nav?: NavEntry[];              // punkter under "Verktyg" i sidopanelen
  routes?: ModuleRoute[];        // egna vyer: focus under /flode, standard och full under /verktyg (ADR-004, ADR-019, ADR-025)
  pages?: PageManifest[];        // redaktionella sidor under "Sidor" (ADR-016)
  slots?: {
    fokus?: FokusProvider;       // fokusnyheten under AI-fältet, tills den stängs (ADR-021)
    genvag?: Genvag[];           // knappar bredvid hälsningen; ai: true ger ✦ (ADR-025)
    kpi?: Widget[];              // nyckeltalsraden, helbredd
    main?: Widget[];             // vänsterspalten
    side?: Widget[];             // högerspalten
    actions?: ActionsProvider;   // rader i "Att agera på"
  };
  notifications?: NotificationsProvider;  // notiser i klockan (ADR-020)
  ai?: {
    suggestions?: (ctx: UserContext) => Suggestion[];   // renderas i AI-fältet
    intents?: IntentHandler[];                          // AI-fältet matchar dem
                                                        // före modellen
  };
}
```

## Sloten

Slot | Plats | Form | Använd när
fokus | Under AI-fältet på startsidan, tills den stängs | FokusProvider | Något alla ska se innan de börjar – en viktig nyhet, en störning. Data, inte komponent (ADR-021).
genvag | Bredvid hälsningen på startsidan | Genvag[] | En arbetsplats man ofta går till, som Min AI. NavEntry plus ai?: boolean; skalet ritar knappen (ADR-025).
kpi | Fyra spår över hela arbetsytan | Widget[] | Ett mätvärde som tål att stå ensamt.
main | Vänsterspalten, minmax(0, 1fr) | Widget[] | Kortet är sidans huvudinnehåll eller behöver bredd.
side | Högerspalten, 36 rem | Widget[] | Stödinformation: idag, saldon, korta listor.
actions | Kortet "Att agera på" | ActionsProvider | Något kräver en handling av användaren.

order styr ordningen inom en slot – lägre först. Utan order gäller registreringsordningen i registry.ts.

Widget och ActionItem:
```
interface Widget {
  id: string;
  roles?: Role[];      // snävare än modulens
  order?: number;      // lägre först
  Component: (props: { ctx: UserContext }) => ReactNode | Promise<ReactNode>;
}

interface ActionItem {
  id: string;
  status: RowStatus;                       // krit | warn | info | ok | neutral
  title: string;
  meta?: string;
  aiNote?: string;                         // "✦ Utkast klart" – märker AI-underlag
  chip?: { tone: ChipTone; label: string };
  action: { label: string; href: string; variant?: "primary" | "ghost" };
}
```

actions är en funktion, inte en lista: raderna hämtas per användare och kan kräva ett anrop. Modulen äger raden, kortet agera äger ordningen (ADR-007).

## Registrera modulen

1. Skapa paketet
   packages/modules/<namn> med samma inre form som de befintliga: manifest.ts, components/, data/, index.ts.
2. Lägg till i workspaces
   Roten har redan packages/modules/* som workspace. Lägg beroendet i apps/projekt/package.json och paketet i transpilePackages i next.config.ts.
   ```
   // apps/projekt/next.config.ts
   transpilePackages: ["@startsidan/ui", …, "@startsidan/modul-<namn>"],
   ```
3. En rad i registret
   Ordningen i registry.ts styr nav-ordningen och fallordningen i sloten när order saknas.
   ```
   // apps/portal/src/shell/registry.ts
   import { manifest as nyModul } from "@startsidan/modul-<namn>";
   
   export const registry: ModuleManifest[] = [oversikt, agera, …, nyModul];
   ```
4. Verifiera rollfiltreringen
   Växla demoanvändare i användarmenyn. Ser båda rollerna rätt saker är kontraktet rätt använt.

## Namngivning
Två språk, med en gräns som går vid kontraktet. Blandas de går koden att läsa men inte att känna igen.

Var | Språk | Exempel
Modulens domänkod: data, typer, fält | Svenska | hamtaLedighet, poster, rubrik, etikett, ton, klaraForAttest
Modulens komponenter | Svenska | Ledighetskort, AttAgeraPa, Nyckeltal
module-sdk och skalet | Engelska | ModuleManifest, UserContext, widgetsFor, ActionItem, roles, order
Filnamn i moduler | Som komponenten | components/Ledighetskort.tsx, data/index.ts
All text användaren ser | Svenska | "Väntar på chef", "Visa alla →"
Commit-ämnen | Svenska | feat(kalender): veckovy i högerspalten

- Gränsen går vid kontraktet: allt som module-sdk namnger är engelskt, allt modulen själv namnger är svenskt.
- Översätt aldrig ett SDK-fält. En modul skriver roles och order, inte roller och ordning.
- Svenska identifierare skrivs utan diakriter i kod: hamtaArenden, inte hämtaÄrenden.
- Kommentarer är svenska och förklarar varför, inte vad.

## Regler för moduler

- En modul importerar aldrig en annan modul. Behöver två moduler samma sak går det via module-sdk eller via slots.
- En modul skriver aldrig i shell.css. Skalets krom är porterad 1:1 från prototypen.
- Widgeten är en server component om den inte behöver interaktion.
- Mockdata ligger i modulens data/ och har mikrotjänsternas form, bakom ett adapterinterface.
- Kommer innehållet från en modell märks det: aiNote på raden, AiChip i vyn.

Hela portalen i samma form: /dev/llms.txt · /dev/spec.json