foxygit / RPA-Remisser Log in
commits tags

/docs/wiki.html · 49.25 KB

raw
<!DOCTYPE html>
<html lang="sv">
<head>
<meta charset="UTF-8">
<title>RPA-verktyg – Wiki</title>
<style>
  :root {
    color-scheme: light dark;
    --bg: #f5f5f7;
    --panel: #ffffff;
    --border: #d8d8dd;
    --text: #1c1c1e;
    --muted: #6b6b70;
    --accent: #2f6fed;
    --accent-text: #ffffff;
    --danger: #d1453b;
    --ok: #3aa757;
    --code-bg: #111114;
    --code-text: #d6d6d8;
    --tag-bg: #eef2fb;
  }
  @media (prefers-color-scheme: dark) {
    :root {
      --bg: #1c1c1e;
      --panel: #2a2a2d;
      --border: #444448;
      --text: #f2f2f3;
      --muted: #9a9a9e;
      --tag-bg: #232b3d;
    }
  }
  * { box-sizing: border-box; }
  body {
    margin: 0;
    font-family: system-ui, -apple-system, "Segoe UI", sans-serif;
    background: var(--bg);
    color: var(--text);
    line-height: 1.55;
  }
  a { color: var(--accent); }
  code {
    font-family: ui-monospace, Consolas, monospace;
    background: var(--tag-bg);
    padding: 1px 5px;
    border-radius: 4px;
    font-size: 0.92em;
  }
  pre {
    background: var(--code-bg);
    color: var(--code-text);
    padding: 12px 14px;
    border-radius: 8px;
    overflow-x: auto;
    font-family: ui-monospace, Consolas, monospace;
    font-size: 13px;
    line-height: 1.5;
  }
  pre code { background: none; padding: 0; color: inherit; }
  .layout {
    display: grid;
    grid-template-columns: 250px minmax(0, 1fr);
    min-height: 100vh;
  }
  nav.toc {
    position: sticky;
    top: 0;
    align-self: start;
    height: 100vh;
    overflow-y: auto;
    background: var(--panel);
    border-right: 1px solid var(--border);
    padding: 20px 16px;
  }
  nav.toc h2 { font-size: 14px; margin: 0 0 12px; }
  nav.toc ul { list-style: none; margin: 0; padding: 0; }
  nav.toc li { margin: 2px 0; }
  nav.toc a {
    display: block;
    padding: 5px 8px;
    border-radius: 6px;
    text-decoration: none;
    color: var(--text);
    font-size: 13px;
  }
  nav.toc a:hover { background: var(--tag-bg); }
  nav.toc .sub { padding-left: 14px; font-size: 12px; color: var(--muted); }
  main {
    padding: 32px 40px 80px;
    max-width: 880px;
  }
  main h1 { margin-top: 0; }
  section { margin-bottom: 48px; scroll-margin-top: 16px; }
  section h2 {
    border-bottom: 1px solid var(--border);
    padding-bottom: 8px;
    margin-top: 0;
  }
  h3 { margin-bottom: 6px; }
  h3 code { font-size: 1em; }
  .hint {
    background: var(--tag-bg);
    border-left: 3px solid var(--accent);
    padding: 10px 14px;
    border-radius: 0 8px 8px 0;
    font-size: 14px;
    margin: 12px 0;
  }
  .warn {
    background: var(--tag-bg);
    border-left: 3px solid var(--danger);
    padding: 10px 14px;
    border-radius: 0 8px 8px 0;
    font-size: 14px;
    margin: 12px 0;
  }
  table {
    width: 100%;
    border-collapse: collapse;
    font-size: 13.5px;
    margin: 10px 0 20px;
  }
  th, td {
    text-align: left;
    padding: 7px 10px;
    border-bottom: 1px solid var(--border);
    vertical-align: top;
  }
  th { color: var(--muted); font-weight: 600; font-size: 12px; text-transform: uppercase; letter-spacing: 0.02em; }
  .step-card {
    background: var(--panel);
    border: 1px solid var(--border);
    border-radius: 10px;
    padding: 16px 18px;
    margin: 14px 0;
  }
  .step-card h3 { display: flex; align-items: center; gap: 8px; }
  .tag {
    display: inline-block;
    background: var(--tag-bg);
    color: var(--accent);
    border-radius: 999px;
    padding: 1px 9px;
    font-size: 11px;
    font-weight: 600;
  }
  .field-list { margin: 8px 0 0; padding-left: 18px; font-size: 13.5px; }
  .field-list li { margin: 3px 0; }
  .toplink { font-size: 12px; }
  footer { color: var(--muted); font-size: 12px; margin-top: 40px; }
  @media (max-width: 760px) {
    .layout { grid-template-columns: 1fr; }
    nav.toc { position: static; height: auto; border-right: none; border-bottom: 1px solid var(--border); }
    main { padding: 24px 20px 60px; }
  }
</style>
</head>
<body>
<div class="layout">
<nav class="toc">
  <h2>RPA-verktyg – Wiki</h2>
  <ul>
    <li><a href="#oversikt">Översikt</a></li>
    <li><a href="#komigang">Kom igång</a></li>
    <li><a href="#stegreferens">Stegreferens</a>
      <ul class="sub">
        <li><a href="#steg-navigation">Navigation &amp; väntan</a></li>
        <li><a href="#steg-interaktion">Sidinteraktion</a></li>
        <li><a href="#steg-variabler">Variabler &amp; data</a></li>
        <li><a href="#steg-kontrollflode">Kontrollflöde</a></li>
      </ul>
    </li>
    <li><a href="#villkor">Villkor (If)</a></li>
    <li><a href="#interpolering">Variabler i text ({var})</a></li>
    <li><a href="#loop">Loop: DOM vs. lista</a></li>
    <li><a href="#recept">Recept (arbetsflöden)</a></li>
    <li><a href="#etiketter">Grenar, sammanslagning &amp; loopar</a></li>
    <li><a href="#subfloden">Subflöden &amp; flikar</a></li>
    <li><a href="#felsokning">Felsökning</a></li>
    <li><a href="#datamappar">Datamappar</a></li>
    <li><a href="#sprak">Språk (i18n)</a></li>
    <li><a href="#installningar">Inställningar &amp; export</a></li>
    <li><a href="#git">Version &amp; git</a></li>
  </ul>
</nav>
<main>
  <h1>RPA-verktyg – Wiki</h1>
  <p class="hint">
    Referensdokumentation för det interna RPA-verktyget (Playwright + webbaserad
    flödesbyggare). Öppna den här sidan lokalt i en webbläsare när du bygger
    flöden. Se <code>README.md</code> för installation och kom-igång-instruktioner
    – den här sidan är en djupare referens över alla stegtyper, villkor och
    mekanismer.
  </p>

  <section id="oversikt">
    <h2>Översikt</h2>
    <p>
      Ett flöde är en JSON-fil (<code>data/flows/&lt;namn&gt;.json</code>) i
      <strong>grafform</strong>: <code>{"format":"graph", "nodes":[…], "edges":[…],
      "start":"&lt;nod-id&gt;"}</code>. Varje <strong>nod</strong> har ett
      <code>id</code>, en <code>type</code> och typ-specifika fält (platt – ingen
      nästling). <strong>Kanterna</strong> bestämmer körordningen. Webb-UI:t
      (<code>python run_webui.py</code>,
      <a href="http://127.0.0.1:8765">http://127.0.0.1:8765</a>) bygger grafen
      visuellt – dra kablar mellan noderna.
    </p>
    <p>
      Gamla flöden i det nästlade <code>{"steps":[…]}</code>-formatet läses
      fortfarande; de migreras automatiskt till grafform vid inläsning och skrivs
      om vid nästa sparning.
    </p>
    <p>
      Motorn är <code>rpa/graph_engine.py</code> – <code>run_graph()</code> börjar
      på startnoden och följer kanterna. Efter en vanlig nod följs handtaget
      <code>out</code>. <code>if</code>-noder har ett handtag per gren
      (<code>case0</code>, <code>case1</code>, … + <code>else</code>);
      <code>loop</code>-noder har <code>each</code> (en gång per varv) och
      <code>done</code> (efteråt), och kroppens sista nod kopplas tillbaka till
      loop-nodens <code>in</code>. <code>rpa/flow_engine.py</code> är kvar som
      omslag och kör de enskilda stegen.
    </p>
  </section>

  <section id="komigang">
    <h2>Kom igång</h2>
    <ol>
      <li><code>python -m venv .venv</code> &amp; <code>pip install -r requirements.txt</code> &amp; <code>python -m playwright install chromium</code></li>
      <li><code>python -m rpa.codegen &lt;url&gt;</code> för att hitta exakta selectors mot en riktig, inloggad sida (öppnar en inspector, ingen gissning behövs).</li>
      <li><code>python run_webui.py</code> och öppna <code>http://127.0.0.1:8765</code> för att bygga flödet.</li>
      <li>"Kör flöde" sparar och kör flödet i en synlig webbläsare, med live-logg och en variabelpanel.</li>
    </ol>
    <p>Fullständiga instruktioner finns i <code>README.md</code> i projektroten.</p>
  </section>

  <section id="stegreferens">
    <h2>Stegreferens</h2>
    <p>
      <code>by</code>-fältet (var förekommande) styr hur ett element hittas:
      <code>text</code> (skiftlägesokänslig delsträng), <code>css</code>
      (CSS-selector, stödjer Playwrights utökningar som
      <code>:text-is()</code>, <code>:has-text()</code>, <code>:scope</code>) eller
      <code>role</code> (ARIA-roll + tillgängligt namn). <code>scope</code> är
      <code>page</code> (hela sidan, standard) eller <code>item</code> (bara
      inom aktuell loop-post). <code>nth</code> väljer vilken matchning
      (0 = första).
    </p>

    <h3>CSS-selektorer: flera varianter i samma sträng</h3>
    <p>En <code>css</code>-selektor kan vara en <strong>kommaseparerad lista</strong> –
      varje del är en egen fullständig selektor, och elementet matchar om
      <em>någon</em> del gör det. Praktiskt för att täcka flera filändelser:</p>
    <pre><code>a[href$=".png"], a[href$=".jpg"], a[href$=".jpeg"], a[href$=".gif"]</code></pre>
    <ul>
      <li><strong>Skiftläge:</strong> attributvärden matchas skiftlägeskänsligt –
        <code>.JPG</code> matchar inte <code>.jpg</code>. Lägg till <code>i</code>-flaggan
        (stöds i Chromium): <code>a[href$=".jpg" i]</code>.</li>
      <li><strong><code>$=</code> vs <code>*=</code>:</strong> <code>$=".png"</code> kräver
        att värdet <em>slutar</em> på <code>.png</code> – matchar inte
        <code>bild.png?token=…</code>. Använd <code>*=".png"</code> (innehåller) då.</li>
      <li>Ofta stabilast: matcha på en <strong>ikon-/typklass</strong> i stället för
        filändelsen, t.ex. <code>.ids-icon-file-image</code> (alla bildtyper) eller
        <code>.ids-icon-file-pdf</code> – en selektor, oberoende av skiftläge och
        query-strängar.</li>
    </ul>
    <p>Samma kommalista fungerar överallt där <code>by=css</code> gäller, inklusive
      <code>if</code>-villkorens <code>value</code>.</p>

    <h3 id="steg-navigation">Navigation &amp; väntan</h3>

    <div class="step-card">
      <h3><code>goto</code> <span class="tag">Gå till URL</span></h3>
      <ul class="field-list"><li><code>url</code> – adressen att navigera till</li></ul>
    </div>

    <div class="step-card">
      <h3><code>wait</code> <span class="tag">Vänta (sekunder)</span></h3>
      <ul class="field-list"><li><code>seconds</code> – fast paus</li></ul>
    </div>

    <div class="step-card">
      <h3><code>wait_for_load</code> <span class="tag">Vänta på sidladdning</span></h3>
      <p>Väntar på <code>domcontentloaded</code>. Inga fält.</p>
    </div>

    <div class="step-card">
      <h3><code>go_back</code> <span class="tag">Gå tillbaka</span></h3>
      <p>Webbläsarens "bakåt" (historik). Inga fält. Praktiskt efter att ha
        klickat in på en post från en lista (t.ex. för att öppna en detaljvy),
        för att komma tillbaka till listan och fortsätta en loop.</p>
    </div>

    <div class="step-card">
      <h3><code>wait_for_login</code> <span class="tag">Vänta på inloggning</span></h3>
      <ul class="field-list"><li><code>timeout</code> – sekunder (default 180)</li></ul>
      <p>Pollar sidan tills någon text i <code>config.LOGGED_IN_INDICATORS</code>
        syns (eller ger upp). Fungerar för BankID, 2FA eller annan manuell
        inloggning – verktyget bryr sig bara om att en inloggad-indikator dyker
        upp, inte om <em>hur</em> du loggade in.</p>
    </div>

    <div class="step-card">
      <h3><code>wait_for_selector</code> <span class="tag">Vänta på element</span></h3>
      <ul class="field-list">
        <li><code>by</code>, <code>value</code>, <code>role?</code>, <code>scope?</code>, <code>nth?</code></li>
        <li><code>timeout</code> – sekunder (default 15)</li>
      </ul>
    </div>

    <h3 id="steg-interaktion">Sidinteraktion</h3>

    <div class="step-card">
      <h3><code>click</code> <span class="tag">Klicka</span></h3>
      <ul class="field-list"><li><code>by</code>, <code>value</code>, <code>role?</code>, <code>scope?</code>, <code>nth?</code></li></ul>
    </div>

    <div class="step-card">
      <h3><code>type</code> <span class="tag">Skriv i textfält</span></h3>
      <ul class="field-list">
        <li><code>by</code>, <code>value</code>, <code>role?</code>, <code>scope?</code>, <code>nth?</code></li>
        <li><code>text</code> – texten som skrivs (stödjer <a href="#interpolering">{variabel}</a>)</li>
      </ul>
    </div>

    <div class="step-card">
      <h3><code>press_key</code> <span class="tag">Tryck tangent</span></h3>
      <ul class="field-list">
        <li><code>key</code> – t.ex. <code>Enter</code>, <code>Tab</code>, <code>Escape</code></li>
        <li><code>value</code> tomt → tangenten trycks mot hela sidan. Annars <code>by/value/role/scope/nth</code> för att trycka på ett specifikt element.</li>
      </ul>
    </div>

    <div class="step-card">
      <h3><code>select_option</code> <span class="tag">Välj i dropdown</span></h3>
      <ul class="field-list">
        <li><code>by</code>, <code>value</code>, <code>role?</code>, <code>scope?</code>, <code>nth?</code></li>
        <li><code>option</code> – synlig text på alternativet</li>
      </ul>
    </div>

    <div class="step-card">
      <h3><code>check</code> <span class="tag">Kryssa checkbox/radio</span></h3>
      <ul class="field-list">
        <li><code>by</code>, <code>value</code>, <code>role?</code>, <code>scope?</code>, <code>nth?</code></li>
        <li><code>checked</code> – <code>true</code> (kryssa i, default) eller <code>false</code> (kryssa ur)</li>
      </ul>
    </div>

    <div class="step-card">
      <h3><code>download</code> <span class="tag">Ladda ner</span></h3>
      <ul class="field-list"><li><code>by</code>, <code>value</code>, <code>role?</code>, <code>scope?</code>, <code>nth?</code></li></ul>
      <p>Klickar och sparar den nedladdade filen i <code>data/downloads/</code>.
        Misslyckad nedladdning loggas men avbryter inte flödet.</p>
    </div>

    <div class="step-card">
      <h3><code>save_attachment</code> <span class="tag">Ladda ner bilaga</span></h3>
      <ul class="field-list">
        <li><code>by</code>, <code>value</code>, <code>role?</code>, <code>scope?</code>, <code>nth?</code> – pekar ut länken (eller ett element som innehåller en <code>&lt;a&gt;</code>). I en loop över bilagerader: <code>by=css</code>, <code>scope=item</code>, <code>value=a[href]</code>.</li>
        <li><code>filename?</code> – filnamn i <code>data/downloads/</code> (tomt = tas från filens URL; stödjer <code>{variabel}</code>). Krock döps om till <code>namn (2).ext</code>.</li>
        <li><code>var?</code> – lägger den sparade sökvägen i en <strong>listvariabel</strong> (så flera bilagor samlas i en lista)</li>
        <li><code>timeout?</code> – sekunder (default 15)</li>
      </ul>
      <p>Hittar en <code>&lt;a&gt;</code> och: har den en riktig <code>href</code>
        (t.ex. PDF-bilagor) hämtas filen direkt via webbläsarens session (behåller
        inloggningen, ingen ny flik/skrivardialog). Är det en <code>javascript:</code>-länk
        (t.ex. bild­bilagor) klickas den i stället – öppnas då en <strong>popup med en
        nedladdningslänk</strong> följer steget den och hämtar filen därifrån (popupens
        URL, en fil-/nedladdningslänk i den, eller ett klick som startar nedladdningen).
        0 bilagor → <code>list</code> ger antal 0 och loopen kör inga varv. Misslyckad
        hämtning loggas men avbryter inte flödet.</p>
      <p><strong>Olika hantering för PDF vs. bild:</strong> lägg ett <code>if</code>
        i loopen med villkoret <code>element_exists</code>, <code>by=css</code>,
        <code>scope=item</code>, <code>value</code> <code>.ids-icon-file-pdf</code>
        (eller <code>a[href$=".pdf"]</code>) för PDF-grenen och <code>else</code> för
        bild. Behövs oftast inte – ett enda <code>save_attachment</code> klarar båda –
        men gör det möjligt att t.ex. bara spara PDF:er, eller döpa dem olika.</p>
      <pre><code>list            value "etj-file-attachment-link", var "bilaga_element"
loop over "bilaga_element":
  save_attachment  by=css  scope=item  value="a[href]"  var="bilagor"
save_var        var "bilagor", filename "bilagor.json"
run_script      scripts/merge_and_print.py
                --files-from bilagor.json --out data/downloads/klar.pdf --print</code></pre>
    </div>

    <div class="step-card">
      <h3><code>capture_pdf</code> <span class="tag">Fånga PDF (skriv ut-knapp)</span></h3>
      <ul class="field-list">
        <li><code>by</code>, <code>value</code>, <code>role?</code>, <code>scope?</code>, <code>nth?</code> – triggern att klicka (t.ex. "SKRIV UT"). Lämna <code>value</code> tomt för att fånga sidan som redan visas.</li>
        <li><code>filename?</code> – filnamn i <code>data/downloads/</code> (default <code>utskrift.pdf</code>, stödjer <code>{variabel}</code>)</li>
        <li><code>var?</code> – variabel att lägga den sparade sökvägen i (praktiskt för ett efterföljande <code>run_script</code>-steg)</li>
        <li><code>timeout?</code> – sekunder att vänta på ett PDF-nätverkssvar (default 15)</li>
      </ul>
      <p>För "Skriv ut"-knappar som öppnar webbläsarens skrivardialog i stället för
        att ladda ner en fil. Steget neutraliserar <code>window.print</code>, klickar ev.
        triggern och fångar PDF:en i tur och ordning via: (1) ett PDF-nätverkssvar,
        (2) en inbäddad PDF i sidan (<code>embed</code>/<code>iframe</code>/<code>object</code>/<code>a</code>
        med <code>.pdf</code>), (3) Chrome <em>printToPDF</em> av sidan som den ser ut
        (fungerar även i icke-headless läge). Misslyckad fångst loggas men avbryter
        inte flödet.</p>
    </div>

    <div class="step-card">
      <h3><code>screenshot</code> <span class="tag">Ta skärmdump</span></h3>
      <ul class="field-list"><li><code>name?</code> – filnamn utan <code>.png</code> (default <code>screenshot</code>)</li></ul>
      <p>Sparas i <code>data/screenshots/</code>.</p>
    </div>

    <h3 id="steg-variabler">Variabler &amp; data</h3>

    <div class="step-card">
      <h3><code>set_var</code> <span class="tag">Sätt variabel</span></h3>
      <ul class="field-list"><li><code>var</code>, <code>text</code> (stödjer {variabel})</li></ul>
    </div>

    <div class="step-card">
      <h3><code>increment</code> <span class="tag">Öka variabel</span></h3>
      <ul class="field-list">
        <li><code>var</code> – variabeln att öka</li>
        <li><code>by?</code> – steg (default <code>1</code>, negativt värde minskar)</li>
      </ul>
      <p>Saknad eller icke-numerisk variabel behandlas som <code>0</code>. Lägg ett
        sådant steg sist i en <code>loop</code> så blir <code>{var}</code> 1, 2, 3 …
        och kan användas för att särskilja filnamn
        (<code>capture_pdf</code> <code>filename</code> stödjer <code>{variabel}</code>).
        Behöver du bara varvnumret räcker loopens inbyggda <code>index_var</code>
        (default <code>{index}</code>).</p>
    </div>

    <div class="step-card">
      <h3><code>clear_var</code> <span class="tag">Nollställ variabel</span></h3>
      <ul class="field-list"><li><code>var</code> – variabeln att ta bort</li></ul>
      <p>Nästa <code>list_append</code> / <code>save_attachment</code> / <code>extract_row</code>
        skapar den på nytt som tom lista. Lägg först i en <code>loop</code> för att
        nollställa en samlar-lista per varv (t.ex. bilagor per ärende).</p>
    </div>

    <div class="step-card">
      <h3><code>extract_text</code> <span class="tag">Läs text till variabel</span></h3>
      <ul class="field-list"><li><code>by</code>, <code>value</code>, <code>role?</code>, <code>scope?</code>, <code>nth?</code>, <code>var</code></li></ul>
      <p>Läser <em>ett</em> elements text till en variabel (skalärt textvärde).</p>
    </div>

    <div class="step-card">
      <h3><code>extract_row</code> <span class="tag">Extrahera rad (flera kolumner)</span></h3>
      <ul class="field-list">
        <li><code>var</code> – listvariabel att lägga raden i</li>
        <li><code>scope?</code> – normalt <code>item</code> (körs inuti en loop)</li>
        <li><code>fields</code> – lista av <code>{name, by, value, role?}</code>, ett per kolumn</li>
      </ul>
      <p>
        Läser <strong>flera namngivna fält</strong> (celler) från aktuell post i
        ett svep och lägger dem som <strong>en</strong> rad-post (dict) i
        listvariabeln – motsvarar Power Automates "Extract data from table". Ger
        strukturerade kolumner istället för en textklump, vilket gör att
        <code>save_var</code> skriver riktiga kolumnrubriker i CSV (fältnamnen)
        och att en enskild kolumn kan refereras senare med
        <code>{item.Kolumnnamn}</code> (se <a href="#interpolering">interpolering</a>).
      </p>
      <pre><code>{
  "type": "extract_row",
  "var": "Tabell_lista",
  "scope": "item",
  "fields": [
    { "name": "Uppdaterat", "by": "css", "value": "td:nth-child(1)" },
    { "name": "Ärendetyp",  "by": "css", "value": "td:nth-child(2)" },
    { "name": "Status",     "by": "css", "value": "td:nth-child(6)" }
  ]
}</code></pre>
    </div>

    <div class="step-card">
      <h3><code>list_append</code> <span class="tag">Lägg till i lista</span></h3>
      <ul class="field-list">
        <li><code>var</code> – listvariabel (skapas automatiskt)</li>
        <li>Antingen <code>by/value/role/scope/nth</code> (läser elementets text) <em>eller</em> <code>text</code> (literalt värde, stödjer {variabel}) om <code>value</code> lämnas tomt</li>
      </ul>
      <p>Lägger till <strong>ett</strong> textvärde per anrop – jämför med
        <code>extract_row</code> som lägger till en hel namngiven rad i ett
        anrop.</p>
    </div>

    <div class="step-card">
      <h3><code>save_var</code> <span class="tag">Spara variabel (JSON/CSV)</span></h3>
      <ul class="field-list">
        <li><code>var</code> – variabeln att spara</li>
        <li><code>filename</code> – i <code>data/exports/</code>, inga sökvägar tillåtna</li>
        <li><code>format?</code> – <code>json</code>/<code>csv</code>, gissas från filändelsen om tomt</li>
        <li><code>mode?</code> – <code>overwrite</code> (default) eller <code>append</code></li>
      </ul>
      <p>
        CSV: en lista av dicts (t.ex. från <code>extract_row</code>) ger riktiga
        kolumnrubriker från dict-nycklarna. En lista av vanliga textvärden
        (t.ex. från <code>list_append</code> med literalt värde) får en enda
        kolumn med rubriken <code>value</code> – det är <em>inte</em> en bugg,
        CSV kräver en kolumnrubrik och "value" är namnet på den enda kolumnen
        när posterna saknar egna fältnamn. Använd <code>extract_row</code> om du
        vill ha meningsfulla kolumnnamn istället.
      </p>
    </div>

    <div class="step-card">
      <h3><code>load_var</code> <span class="tag">Läs in variabel (JSON/CSV)</span></h3>
      <ul class="field-list"><li><code>var</code>, <code>filename</code> (samma regler som <code>save_var</code>)</li></ul>
      <p>CSV med en enda kolumn "value" läses tillbaka som en lista av textvärden;
        CSV med flera kolumner läses tillbaka som en lista av dicts (samma
        struktur som <code>extract_row</code> producerar) – round-trip fungerar
        alltså sömlöst i båda riktningarna.</p>
    </div>

    <div class="step-card">
      <h3><code>run_script</code> <span class="tag">Kör Python-script</span></h3>
      <ul class="field-list">
        <li><code>path</code> – relativ mot projektroten, eller absolut</li>
        <li><code>args?</code> – mellanslagsseparerade argument</li>
        <li><code>timeout?</code> – sekunder (default 60)</li>
        <li><code>var?</code> – sparar scriptets stdout (JSON-tolkat om möjligt, annars text)</li>
      </ul>
      <p>Loggar stdout/stderr rad för rad. Flödet stoppas om scriptet avslutar
        med en felkod.</p>
      <div class="warn">Kör med samma rättigheter som webui-processen – bara för
        egna, betrodda script.</div>
    </div>

    <div class="step-card">
      <h3><code>list</code> <span class="tag">Lista poster</span></h3>
      <ul class="field-list">
        <li><code>value</code> – CSS-selector som matchar flera element</li>
        <li><code>var</code> – variabeln att spara resultatet i</li>
      </ul>
      <p>Sparar <code>{selector, count, preview}</code> i variabeln –
        <code>preview</code> är textinnehållet i de tre första träffarna, bara
        för att kunna sanity-checka selectorn i <a href="#felsokning">variabelpanelen</a>
        innan man bygger en hel loop kring den. <code>Loop</code> bryr sig bara
        om <code>selector</code>/<code>count</code>.</p>
    </div>

    <div class="step-card">
      <h3><code>ocr_read</code> <span class="tag">OCR – läs text ur bild/PDF</span></h3>
      <ul class="field-list">
        <li><code>source</code> – <code>pdf</code> (fil i <code>data/downloads/</code>), <code>window</code> (fönstertitel-regex), <code>region</code> (<code>x/y/w/h</code> skärmkoordinater) eller <code>image</code> (fil)</li>
        <li><code>var</code> – variabeln att spara texten i</li>
        <li><code>pattern?</code> – regex; namngiven grupp <code>(?P&lt;value&gt;…)</code> annars hela träffen</li>
        <li><code>near?</code> / <code>near_mode?</code> – returnera värdet <code>same_line</code> (efter en etikett) eller <code>below</code> (raden under)</li>
        <li><code>lang?</code> – OCR-språk, default <code>sv-SE</code></li>
      </ul>
      <p>Windows-only (winocr = Windows.Media.Ocr + pymupdf för PDF). Kräver att
        OCR-språket är installerat i Windows – annars ett fel med exakt
        DISM-kommandot. En PDF med textlager läses direkt utan OCR.</p>
    </div>

    <div class="step-card">
      <h3><code>lookup</code> <span class="tag">Slå upp i tabell</span></h3>
      <ul class="field-list">
        <li><code>from_var</code> – en variabel som håller en JSON-map (från <code>Läs in variabel</code>); punktnotation <code>cfg.elvis.tabell</code> går bra</li>
        <li><code>file</code> – …eller en JSON-fil i <code>data/exports/</code></li>
        <li><code>key</code> – nyckel att slå upp, stödjer <code>{variabel}</code> (t.ex. <code>{grupp}|{spec}</code>)</li>
        <li><code>var</code> – resultatet (kan vara ett objekt → <code>{var.fält}</code> funkar sen)</li>
        <li><code>ci?</code> – skiftlägesokänsligt (default på); <code>on_missing?</code> – <code>error</code> / <code>empty</code> / ett defaultvärde</li>
      </ul>
    </div>

    <div class="step-card">
      <h3><code>merge_json</code> <span class="tag">Skriv nyckel till JSON-fil</span></h3>
      <ul class="field-list">
        <li><code>file</code> – JSON-fil i <code>data/exports/</code> (skapas om den saknas)</li>
        <li><code>key</code> / <code>value</code> – <code>data[key] = value</code>, båda stödjer <code>{variabel}</code></li>
      </ul>
      <p>För kom-ihåg-tabeller: fråga användaren om ett värde med <code>prompt</code>
        och spara det här, så finns det nästa körning (läs tillbaka med
        <code>Läs in variabel</code> + <code>lookup</code>).</p>
    </div>

    <div class="step-card">
      <h3><code>read_url</code> <span class="tag">Läs aktuell URL</span></h3>
      <ul class="field-list">
        <li><code>var</code> – aktuell sidas URL</li>
        <li><code>pattern?</code> – regex-utplock, t.ex. ett ärende-id ur adressen</li>
      </ul>
    </div>

    <h3 id="steg-kontrollflode">Kontrollflöde</h3>

    <div class="step-card">
      <h3><code>prompt</code> <span class="tag">Fråga användaren (paus)</span></h3>
      <ul class="field-list">
        <li><code>mode</code> – <code>confirm</code> (OK/Avbryt; Avbryt stoppar flödet), <code>input</code> (ett textfält → <code>var</code>), <code>fields</code> (visa de angivna variablerna redigerbara)</li>
        <li><code>message</code> – text, stödjer <code>{variabel}</code></li>
        <li><code>fields?</code> – komma-separerade variabelnamn (för <code>mode=fields</code>)</li>
      </ul>
      <p>Pausar körningen och visar en ruta på skärmen (egen process). <code>fields</code>
        är till för att låta användaren <strong>rätta OCR-fel</strong> innan värdena
        skrivs vidare.</p>
    </div>

    <div class="step-card">
      <h3><code>stop</code> <span class="tag">Stoppa flödet</span></h3>
      <ul class="field-list"><li><code>message</code> – felmeddelande, stödjer <code>{variabel}</code></li></ul>
      <p>Avbryter körningen (t.ex. när ett ärende inte kan hanteras mekaniskt).</p>
    </div>

    <div class="step-card">
      <h3><code>loop</code> <span class="tag">Loop (för varje post)</span></h3>
      <ul class="field-list">
        <li><code>over</code> – variabelnamn att loopa över</li>
        <li><code>item_var?</code> – variabelnamn för aktuellt värde vid loop över en lista (default <code>item</code>)</li>
        <li><code>index_var?</code> – variabelnamn för varvräknaren <code>1, 2, 3 …</code> (default <code>index</code>); sätts oavsett looptyp och kan användas som <code>{index}</code> i t.ex. filnamn</li>
      </ul>
      <p>Utgångar: <code>each</code> (körs en gång per post, sätter
        <code>item</code>/<code>index</code>) och <code>done</code> (efter sista
        varvet). <strong>Kroppens sista nod måste ha en kant tillbaka till
        loop-nodens <code>in</code></strong> – annars körs varv 0 en gång och
        <code>done</code> nås aldrig. Se <a href="#loop">Loop: DOM vs. lista</a>
        för de två <code>over</code>-lägena.</p>
    </div>

    <div class="step-card">
      <h3><code>if</code> <span class="tag">Om / annars om / annars</span></h3>
      <ul class="field-list">
        <li><code>branches</code> – lista av <code>{condition}</code>, i ordning (första sanna vinner)</li>
      </ul>
      <p>Utgångar: <code>case0</code>, <code>case1</code>, … (en per gren) samt
        <code>else</code> (ingen gren matchade). Koppla varje grens noder till
        respektive utgång. Matchar ingen gren och det saknas en
        <code>else</code>-kant tar den vägen slut. Se <a href="#villkor">Villkor</a>.</p>
    </div>

    <div class="step-card">
      <h3><code>call_flow</code> <span class="tag">Anropa subflöde</span></h3>
      <ul class="field-list">
        <li><code>target</code> – ett externt sparat flödesnamn, ELLER <code>#&lt;subflow-id&gt;</code> för en lokal subgraf i samma fil</li>
        <li><code>in</code> – lista av <code>{name, value}</code>; <code>name</code> = subflödets indata, <code>value</code> stödjer <code>{variabel}</code></li>
        <li><code>out</code> – lista av <code>{from, to}</code>; kopierar subflödets slutvariabel <code>from</code> till anroparens variabel <code>to</code></li>
      </ul>
      <p>Kör ett annat flöde som ett steg (vanliga handtag <code>in</code>/<code>out</code>).
        Subflödet får en <strong>egen tom variabel-namnrymd</strong> som seedas ur
        <code>in</code> (och subflödets deklarerade <code>inputs</code>-default);
        bara det du listar i <code>out</code> följer med tillbaka. Samma webbläsare/
        flik och inställningar som anroparen. Cirkulära anrop (A→B→A) och för djup
        kedja avbryts med <code>FlowError</code>. Se <a href="#subfloden">Subflöden &amp; flikar</a>.</p>
    </div>

    <div class="step-card">
      <h3>Universella fält (alla noder)</h3>
      <ul class="field-list">
        <li><code>enabled</code> – <code>false</code> hoppar över noden (kryssruta i UI:t); körningen fortsätter via <code>out</code>.</li>
        <li><code>label</code> – valfritt namn på noden (visas i körloggen).</li>
        <li><code>on_error</code> – <code>"stop"</code> (default) avbryter körningen om noden misslyckas/får timeout; <code>"continue"</code> loggar felet och går vidare. Väljs i listan bredvid etikettfältet.</li>
      </ul>
      <p class="hint"><code>jump_to_label</code> finns inte längre – rita en kant
        i stället.</p>
    </div>
  </section>

  <section id="villkor">
    <h2>Villkor (If)</h2>
    <p><code>if</code>-noden utvärderar <code>branches</code> i ordning – första
      grenen vars <code>condition</code> är sann följer sin <code>case</code>-utgång
      och resten hoppas över. Ingen gren sann → <code>else</code>-utgången följs om
      den är kopplad,
      annars fortsätter flödet till nästa steg.</p>
    <table>
      <tr><th>kind</th><th>Fält</th><th>Betyder</th></tr>
      <tr><td><code>element_exists</code></td><td><code>by, value, role?, scope?, nth?</code></td><td>Sant om selectorn matchar minst ett element</td></tr>
      <tr><td><code>element_not_exists</code></td><td>samma som ovan</td><td>Sant om selectorn INTE matchar något</td></tr>
      <tr><td><code>var_equals</code></td><td><code>var, text</code></td><td>Sant om variabelns textvärde är exakt <code>text</code></td></tr>
      <tr><td><code>var_not_equals</code></td><td><code>var, text</code></td><td>Sant om variabelns textvärde INTE är <code>text</code></td></tr>
      <tr><td><code>var_contains</code></td><td><code>var, text</code></td><td>Sant om <code>text</code> finns som delsträng i variabeln</td></tr>
    </table>
    <div class="hint">
      Playwrights CSS-utökning <code>:text-is("...")</code> (exakt, skiftlägeskänslig
      matchning) är ofta bättre än <code>by=text</code> (skiftlägesokänslig
      delsträng) när du vill särskilja t.ex. en cell som är exakt
      <code>"Tilldelat"</code> från en som innehåller <code>"Automatiskt tilldelat"</code>.
    </div>
    <pre><code>{
  "id": "kollaTilldelad",
  "type": "if",
  "branches": [
    {
      "condition": {
        "kind": "element_not_exists",
        "by": "css", "scope": "item",
        "value": ":text-is(\"Tilldelat\")"
      }
    }
  ]
}
// kant kollaTilldelad --case0--> (noderna som bara ska köras för rader som INTE
// redan är Tilldelat), och deras sista nod --out--> tillbaka till loopens in.</code></pre>
  </section>

  <section id="interpolering">
    <h2>Variabler i text (<code>{variabelnamn}</code>)</h2>
    <p>Följande fält stödjer <code>{variabelnamn}</code>, ersatt med variabelns
      textvärde vid körning:</p>
    <ul>
      <li><code>by=text</code>/<code>by=role</code> sökvärden (i alla steg som har dem)</li>
      <li><code>type</code>-stegets <code>text</code></li>
      <li><code>set_var</code>-stegets <code>text</code></li>
      <li><code>list_append</code>-stegets literala <code>text</code> (när <code>value</code> är tomt)</li>
      <li><code>capture_pdf</code>-stegets <code>filename</code> (undantaget – övriga stegs filnamn interpoleras inte)</li>
    </ul>
    <p><strong>Fungerar INTE</strong> i CSS-selectors (<code>by=css</code>),
      URL:er eller filnamn (utom <code>capture_pdf</code>, se ovan) – där tas
      <code>{...}</code> bokstavligt.</p>
    <p>Punkt-notation: <code>{variabel.fält}</code> läser ett namngivet fält ur en
      dict-variabel (t.ex. en rad från <code>extract_row</code> eller en inläst
      flerkolumns-CSV). Okänt variabel- eller fältnamn lämnas oersatt i texten
      (bra för att upptäcka stavfel – flödet kraschar inte, men texten blir
      synligt fel i loggen).</p>
    <pre><code>// item = {"Ärendetyp": "Egenremiss", "Status": "Tilldelat"}
"{item.Ärendetyp}"  →  "Egenremiss"
"{item}"            →  '{"Ärendetyp": "Egenremiss", "Status": "Tilldelat"}'  (hela dicten som JSON)
"{okänd_variabel}"  →  "{okänd_variabel}"  (oersatt, ingen krasch)</code></pre>
  </section>

  <section id="loop">
    <h2>Loop: DOM vs. lista</h2>
    <p><code>loop</code> stödjer två helt olika typer av <code>over</code>-variabel:</p>
    <table>
      <tr><th></th><th>DOM-loop (från "Lista poster")</th><th>Listloop (vanlig lista)</th></tr>
      <tr>
        <td>Variabelns form</td>
        <td><code>{selector, count, preview}</code></td>
        <td>En vanlig JSON-array, t.ex. från <code>list_append</code> eller <code>load_var</code></td>
      </tr>
      <tr>
        <td>Vad varje varv ger</td>
        <td>Ett riktigt DOM-element (aktuell rad) – <code>scope=item</code> i inre steg söker inom det</td>
        <td>Inget DOM-element; aktuellt <em>värde</em> läggs i variabeln <code>item</code> (eller <code>item_var</code>)</td>
      </tr>
      <tr>
        <td>Hitta rätt element på sidan</td>
        <td>Automatiskt (<code>scope=item</code>)</td>
        <td>Måste sökas upp manuellt, t.ex. med <code>by=text, value="{item.Namn}"</code></td>
      </tr>
    </table>
    <div class="warn">
      Innan detta stöd fanns kunde <code>loop</code> bara hantera "Lista
      poster"-resultat – att sätta <code>over</code> till en vanlig listvariabel
      gav <code>TypeError: list indices must be integers or slices, not str</code>.
      Det är åtgärdat; loop-steget avgör nu automatiskt vilken typ variabeln har.
    </div>

    <h3>Klicka på ett element i (eller på) aktuell loop-rad</h3>
    <p>I en DOM-loop söker <code>scope=item</code> <em>inom</em> aktuell rad. Oftast
      vill du klicka ett <strong>barn-element</strong> – en länk, en cell, en knapp –
      inte hela raden (rader har sällan någon klick-hanterare):</p>
    <pre><code>list             value "table.ids-data-table tbody tr", var "rader"
loop over "rader":
  click          by=css  scope=item  value="a"                (länken i raden, t.ex. filnamnet)
  click          by=css  scope=item  value="td:nth-child(5)"  (en viss cell)</code></pre>
    <p>Vill du träffa <strong>raden/elementet självt</strong> (t.ex. spara den i en
      lista, eller om hela raden faktiskt är en länk) använd
      <code>value=":scope"</code> – CSS-pseudoklassen för "det element sökningen
      utgår från":</p>
    <pre><code>  list_append    by=css  scope=item  value=":scope"   var "traffar"</code></pre>
    <p>Loggen visar vad som faktiskt träffades, t.ex.
      <code>Klickar: css='a' → &lt;a.ids-link href=/case/123 "Bilresa - aktivitetshäfte.pdf"&gt;</code>
      – jämför det mot vad du menade att klicka.</p>
  </section>

  <section id="recept">
    <h2>Recept (vanliga arbetsflöden)</h2>

    <h3>Ladda ner alla bilagor från ett ärende</h3>
    <p>Bilagorna ligger på ärendets detaljsida. Antalet varierar (0, 1 eller
      flera). PDF-bilagor har en riktig länk; bildbilagor öppnar en popup med en
      nedladdningslänk – <code>save_attachment</code> hanterar båda.</p>
    <pre><code>click            (öppna ärendet, t.ex. td:nth-child(5))
wait             1–2 s
clear_var        var "bilagor"                       (nollställ per ärende)
list             value "etj-file-attachment-link", var "bilaga_element"
loop over "bilaga_element":
  save_attachment  by=css  scope=item  value="a[href]"  var="bilagor"
go_back          (tillbaka till listan inför nästa varv)</code></pre>
    <p>Filerna hamnar i <code>data/downloads/</code> med sina riktiga namn;
      namnkrock döps om till <code>namn (2).ext</code>. <code>bilagor</code> blir
      en lista med sökvägarna.</p>

    <h3>Bara PDF:er, eller olika hantering för PDF vs. bild</h3>
    <pre><code>loop over "bilaga_element":
  if  element_exists  by=css  scope=item  value=".ids-icon-file-pdf":
        save_attachment  by=css  scope=item  value="a[href]"  var="pdf_lista"
  else:
        save_attachment  by=css  scope=item  value="a[href]"  var="bild_lista"</code></pre>
    <p><code>.ids-icon-file-pdf</code> / <code>.ids-icon-file-image</code> är
      ikon­klasserna i raden. Utelämna <code>else</code>-grenen för att hoppa över
      bilder helt.</p>

    <h3>Slå ihop bilagor till en PDF och skriv ut</h3>
    <p>Hjälpskriptet <code>scripts/merge_and_print.py</code> (kräver
      <code>pypdf</code> + <code>Pillow</code>) tar en ordnad blandning av PDF:er
      och bilder: bilder blir centrerade A4-sidor, PDF:er läggs in sida för sida.</p>
    <pre><code>save_var        var "bilagor", filename "bilagor.json"
run_script      path "scripts/merge_and_print.py"
                args "--files-from bilagor.json --out data/downloads/arende_{index}.pdf --print"</code></pre>
    <table>
      <tr><th>Argument</th><th>Betydelse</th></tr>
      <tr><td><code>--files-from &lt;fil&gt;</code></td><td>JSON-array med sökvägar (fil i <code>data/exports/</code> eller en sökväg) – t.ex. det <code>save_var</code> skrev</td></tr>
      <tr><td><code>--file &lt;sökväg&gt;</code></td><td>En fil till (upprepa för fler; ordningen bevaras)</td></tr>
      <tr><td><code>--pdf-latest</code></td><td>Nyaste <code>*.pdf</code> i <code>data/downloads/</code> först</td></tr>
      <tr><td><code>--out &lt;sökväg&gt;</code></td><td>Utfil (default <code>data/downloads/merged.pdf</code>)</td></tr>
      <tr><td><code>--print</code></td><td>Skicka utfilen till skrivare</td></tr>
      <tr><td><code>--printer "Namn"</code></td><td>Specifik skrivare (default: Windows standardskrivare)</td></tr>
    </table>
    <p>Skriptet skriver en rad JSON till stdout
      (<code>{"out": ..., "pages": 3, "printed": true, "skipped": []}</code>) – sätt
      <code>run_script</code>-stegets <code>var</code> för att fånga den.</p>

    <h3>Numrera nedladdade filer i en loop</h3>
    <ul>
      <li>Löpande varvnummer räcker: använd <code>{index}</code> direkt i
        <code>filename</code> (loopen sätter det automatiskt).</li>
      <li>Tät numrering av bara de faktiskt sparade filerna: lägg ett
        <code>increment</code>-steg (<code>var "n"</code>) precis före
        nedladdningen och använd <code>{n}</code> i <code>filename</code>. Nollställ
        med <code>clear_var</code> om räknaren ska börja om per ärende.</li>
    </ul>

    <h3>Göra ett steg "hit or miss"</h3>
    <p>Sätt stegets <code>on_error</code> till <code>"continue"</code> (listan
      bredvid etikettfältet) så loggas ett fel/timeout och nästa steg körs ändå –
      bra för ett <code>wait_for_selector</code> eller <code>click</code> som bara
      ibland är relevant. På en <code>loop</code>/<code>if</code> gäller det hela
      blocket.</p>
  </section>

  <section id="etiketter">
    <h2>Grenar, sammanslagning &amp; loopar</h2>
    <p>I grafen finns inga hopp – du ritar kanter. Vanliga mönster:</p>
    <ul>
      <li><strong>Gren + sammanslagning:</strong> koppla <code>if</code>-nodens
        <code>case</code>-/<code>else</code>-utgångar till varsin nodkedja, och låt
        varje kedjas sista nod peka på samma efterföljande nod.</li>
      <li><strong>Loop:</strong> <code>loop.each</code> → kroppens första nod;
        kroppens sista nod → <code>loop.in</code> (loopback); <code>loop.done</code>
        → det som ska hända efteråt.</li>
      <li>Ett utgångshandtag styr bara <strong>en</strong> kant (motorn följer den
        första). Vill du köra flera saker: kedja dem, eller lägg in en nod som
        samlar ihop.</li>
      <li>Ett skydd stoppar körningen efter väldigt många nodsteg (troligen en
        loop utan avslut) – <code>FlowError</code> istället för att hänga sig.</li>
    </ul>
  </section>

  <section id="subfloden">
    <h2>Subflöden &amp; flikar</h2>
    <p>För att hålla stora flöden läsbara går det att bryta ut delar och
      <strong>anropa</strong> dem med en <code>call_flow</code>-nod:</p>
    <ul>
      <li><strong>Lokala subgrafer</strong> ligger i samma flödesfil under nyckeln
        <code>subflows: [{id, name, nodes, edges, start, inputs}]</code>. I
        canvasen är de egna <strong>flikar</strong> bredvid "Main" (<code>+</code>
        skapar en, dubbelklick döper om). <code>call_flow</code> med
        <code>target: "#&lt;id&gt;"</code> kör en.</li>
      <li><strong>Externa flöden</strong> – ett vanligt sparat flöde anropas med
        <code>target</code> = dess namn.</li>
      <li><strong>Indata/utdata</strong> mappas explicit (<code>in</code>/<code>out</code>
        på noden). Subflödet är en "ren funktion": det ser inte anroparens
        variabler och bara det som listas i <code>out</code> kommer tillbaka.</li>
      <li>Varje subflow-flik har en <strong>"Kör denna flik"</strong>-knapp som kör
        just den subgrafen (med en dialog för dess egna <code>inputs</code>) – bra
        för att testa isolerat.</li>
    </ul>
    <pre><code>// Main: ... -> call_flow(target="#dubbla", in=[{name:"x", value:"{v}"}],
//                        out=[{from:"y", to:"r"}]) -> ...
// subflows: [{ id:"dubbla", name:"Dubbla", inputs:[{name:"x"}],
//              nodes:[{id:"s1", type:"set_var", var:"y", text:"{x}{x}"}], edges:[] }]</code></pre>
  </section>

  <section id="felsokning">
    <h2>Felsökning</h2>
    <table>
      <tr><th>Verktyg</th><th>Vad det gör</th></tr>
      <tr><td>Variabelpanel (webui)</td><td>Klicka på ett variabelnamn efter en körning för att se dess faktiska innehåll (typ + JSON/text)</td></tr>
      <tr><td>Trace (<code>python -m rpa.show_trace</code>)</td><td>Öppnar Playwrights Trace Viewer – skärmdumpar, DOM, nätverk per steg, sparas automatiskt varje körning i <code>data/traces/</code></td></tr>
      <tr><td><code>python -m rpa.codegen &lt;url&gt;</code></td><td>Öppnar en inspector mot en riktig, inloggad sida (samma profil) – hovra/klicka för att få exakta selectors, istället för att gissa</td></tr>
      <tr><td>Håll webbläsaren öppen</td><td>Vid "Kör flöde", välj "Håll öppen vid fel"/"Håll alltid öppen" i dropdownen för att kunna inspektera sidan manuellt efteråt</td></tr>
      <tr><td>Körloggen</td><td>Visar varje stegs faktiska (interpolerade) selector/text-värde, inte den oersatta mallen. För <code>click</code>/<code>type</code>/<code>check</code>/<code>select_option</code>/<code>download</code>/<code>save_attachment</code> visas dessutom vilket element som träffades: <code>→ &lt;tagg.klass href=… "text"&gt;</code></td></tr>
    </table>
  </section>

  <section id="datamappar">
    <h2>Datamappar</h2>
    <p>Allt under <code>data/</code> är gitignorat – kan innehålla
      inloggningssession och personlig information.</p>
    <table>
      <tr><th>Mapp</th><th>Innehåll</th></tr>
      <tr><td><code>data/flows/</code></td><td>Sparade flöden (JSON)</td></tr>
      <tr><td><code>data/browser_profile/</code></td><td>Persistent Chromium-profil (inloggningssession)</td></tr>
      <tr><td><code>data/downloads/</code></td><td>Nedladdade filer ("Ladda ner"-steget)</td></tr>
      <tr><td><code>data/exports/</code></td><td>JSON/CSV från "Spara variabel"/"Läs in variabel"</td></tr>
      <tr><td><code>data/screenshots/</code></td><td>Från "Ta skärmdump"</td></tr>
      <tr><td><code>data/traces/</code></td><td>Playwright-traces, en per körning</td></tr>
      <tr><td><code>data/discover/</code></td><td>HTML/skärmdumpar från <code>rpa.discover</code></td></tr>
    </table>
  </section>

  <section id="sprak">
    <h2>Språk (i18n)</h2>
    <p>Webb-UI:t har engelska som standardspråk med svenska som växlingsbar
      översättning (språkväljare i headern, sparas i webbläsarens
      <code>localStorage</code>). Bara själva gränssnittet (steg-namn,
      fältetiketter, knappar) är översatt – körloggens meddelanden kommer
      alltid på svenska direkt från Python-backend, oavsett valt UI-språk.</p>
  </section>

  <section id="installningar">
    <h2>Inställningar &amp; export</h2>
    <p>"⚙ Settings" i headern öppnar en dialog med körningsinställningar,
      sparade som ett <code>settings</code>-objekt tillsammans med flödets
      <code>nodes</code>/<code>edges</code> i samma JSON-fil (<code>data/flows/&lt;namn&gt;.json</code>):</p>
    <table>
      <tr><th>Nyckel</th><th>Standard</th><th>Betyder</th></tr>
      <tr><td><code>headless</code></td><td><code>false</code></td><td>Kör utan synligt fönster. Kräver en redan giltig inloggningssession.</td></tr>
      <tr><td><code>viewport_width</code> / <code>viewport_height</code></td><td>1280 / 900</td><td>Webbläsarens fönsterstorlek</td></tr>
      <tr><td><code>locale</code></td><td><code>"sv-SE"</code></td><td>Webbläsarens språk/region</td></tr>
      <tr><td><code>slow_mo</code></td><td>0</td><td>Millisekunder paus mellan varje Playwright-åtgärd, för felsökning</td></tr>
      <tr><td><code>notify_on_complete</code></td><td><code>false</code></td><td>Skrivbordsnotis när körningen är klar</td></tr>
    </table>
    <p>"Export as script" laddar ner en fristående <code>.py</code>-fil som
      kör exakt samma flöde (<code>STEPS</code> + <code>SETTINGS</code>
      inbäddade som Python-literaler) genom att importera <code>rpa</code>
      och anropa samma <code>run_flow()</code>-motor som webui:t – ingen
      logik dupliceras, beteendet blir identiskt. Praktiskt för
      schemaläggning (Task Scheduler) av ett flöde byggt i UI:t.</p>
    <pre><code>python mitt_flode.py
python mitt_flode.py --headless          # tvinga headless oavsett sparad inställning
python mitt_flode.py --close-mode never  # håll webbläsaren öppen efteråt</code></pre>
    <div class="warn">Scriptet kräver att <code>rpa</code>-paketet går att
      importera – kör det från projektroten, eller kopiera med hela
      <code>rpa/</code>-mappen.</div>
  </section>

  <section id="git">
    <h2>Version &amp; git</h2>
    <p>Projektet är kopplat till en git-remote
      (<code>git.kristoffersson.info/repos/RPA-Remisser.git</code>). Push kräver
      en personlig token (finns inte sparad i klartext någonstans i repot –
      måste anges vid varje push-tillfälle i den här miljön eftersom
      credential-cachning inte fungerar i sandboxen).</p>
  </section>

  <footer>
    Genererad som en statisk referenssida i <code>docs/wiki.html</code> – öppna
    lokalt i valfri webbläsare. Håll den uppdaterad manuellt när nya stegtyper
    läggs till (källan att stämma av mot är modul-docstringen i
    <code>rpa/flow_engine.py</code>).
  </footer>
</main>
</div>
</body>
</html>