Hoppa till innehållet

Devportalen /portal/dev

Startsideportalens ekosystem för människa och agent

Devportalen

Modulkontraktet

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

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

Tabell: Slot, Plats, Form, Använd när
SlotPlatsFormAnvänd när
fokusUnder AI-fältet på startsidan, tills den stängsFokusProviderNågot alla ska se innan de börjar – en viktig nyhet, en störning. Data, inte komponent (ADR-021).
genvagBredvid hälsningen på startsidanGenvag[]En arbetsplats man ofta går till, som Min AI. NavEntry plus ai?: boolean; skalet ritar knappen (ADR-025).
kpiFyra spår över hela arbetsytanWidget[]Ett mätvärde som tål att stå ensamt.
mainVänsterspalten, minmax(0, 1fr)Widget[]Kortet är sidans huvudinnehåll eller behöver bredd.
sideHögerspalten, 36 remWidget[]Stödinformation: idag, saldon, korta listor.
actionsKortet "Att agera på"ActionsProviderNå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.

Tabell: Var, Språk, Exempel
VarSpråkExempel
Modulens domänkod: data, typer, fältSvenskahamtaLedighet, poster, rubrik, etikett, ton, klaraForAttest
Modulens komponenterSvenskaLedighetskort, AttAgeraPa, Nyckeltal
module-sdk och skaletEngelskaModuleManifest, UserContext, widgetsFor, ActionItem, roles, order
Filnamn i modulerSom komponentencomponents/Ledighetskort.tsx, data/index.ts
All text användaren serSvenska"Väntar på chef", "Visa alla →"
Commit-ämnenSvenskafeat(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.