xetup/web/spec/index.html
X9 Dev f5a5de943a Implement Forgejo review changes: Atera, UCPD, PDF default, OA3, dedup
02-software: add Atera RMM agent install (Invoke-WebRequest + msiexec /qn),
stop UCPD driver before PDF association write, restart after; remove
reference to PDF-DefaultApp scheduled task in header

03-system-registry: correct OneDrive uninstall description - intentional
(pre-installed consumer version only, no policy key, M365 can reinstall)

04-default-profile: OneDrive RunOnce blocking removed, ShowRecent=0,
ShowFrequent=0, FullPath=1 in CabinetState already added in prior session

06-scheduled-tasks: PDF-DefaultApp task removed - PDF set once in step 02

08-activation: add OA3/BIOS embedded key check via SoftwareLicensingService
WMI; key priority: config.json > OA3 firmware > GVLK

web/spec: update all status badges, remove mustfix flags, deduplicate
OneDrive entries across steps 01/03/04, add OA3 row to step-08

web/data/descriptions.json: regenerated (65 items)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-16 09:29:32 +02:00

1213 lines
49 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="cs">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Specifikace &amp; anotace xetup</title>
<link rel="icon" type="image/x-icon" href="/favicon.ico">
<style>
:root {
--bg: #0f1117;
--card: #1a1d27;
--card2: #141720;
--border: #2a2d3a;
--text: #e0e0e0;
--muted: #888;
--accent: #223B47;
--accent-bright: #2d5266;
--green: #2ea043;
--blue: #58a6ff;
--yellow: #d29922;
--red: #da3633;
--purple: #8b949e;
}
* { box-sizing: border-box; margin: 0; padding: 0; }
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: var(--bg);
color: var(--text);
min-height: 100vh;
}
/* ---- HEADER ---- */
header {
border-bottom: 1px solid var(--border);
padding: 1rem 2rem;
display: flex;
align-items: center;
gap: .75rem;
position: sticky;
top: 0;
background: var(--bg);
z-index: 100;
}
.logo-text { font-size: 1.1rem; font-weight: 700; color: #fff; letter-spacing: -.02em; }
.logo-sub { font-size: .8rem; color: var(--muted); margin-left: .2rem; }
header nav { margin-left: auto; display: flex; gap: 1.5rem; }
header nav a { color: var(--muted); text-decoration: none; font-size: .85rem; transition: color .15s; }
header nav a:hover, header nav a.active { color: var(--text); }
/* ---- LAYOUT ---- */
.layout {
display: grid;
grid-template-columns: 220px 1fr;
max-width: 1200px;
margin: 0 auto;
padding: 2rem 1.5rem;
gap: 2rem;
align-items: start;
}
/* ---- SIDEBAR ---- */
.sidebar {
position: sticky;
top: 57px;
border: 1px solid var(--border);
border-radius: 10px;
background: var(--card);
padding: 1rem;
}
.sidebar h4 {
font-size: .72rem;
text-transform: uppercase;
letter-spacing: .08em;
color: var(--muted);
margin-bottom: .75rem;
}
.sidebar a {
display: block;
color: var(--muted);
text-decoration: none;
font-size: .83rem;
padding: .3rem .4rem;
border-radius: 5px;
transition: background .1s, color .1s;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.sidebar a:hover { background: var(--card2); color: var(--text); }
.sidebar-divider { border: none; border-top: 1px solid var(--border); margin: .6rem 0; }
/* ---- CONTENT ---- */
.content h1 {
font-size: 1.7rem;
font-weight: 800;
letter-spacing: -.03em;
color: #fff;
margin-bottom: .4rem;
}
.meta {
font-size: .82rem;
color: var(--muted);
margin-bottom: 2rem;
display: flex;
gap: 1.2rem;
flex-wrap: wrap;
align-items: center;
}
.meta a { color: var(--muted); text-decoration: none; }
.meta a:hover { color: var(--blue); }
/* ---- LEGEND ---- */
.legend {
display: flex;
gap: .6rem;
flex-wrap: wrap;
margin-bottom: 2rem;
padding: .8rem 1rem;
background: var(--card);
border: 1px solid var(--border);
border-radius: 8px;
font-size: .8rem;
}
.legend span { display: flex; align-items: center; gap: .3rem; color: var(--muted); }
/* ---- STEP CARD ---- */
.step {
background: var(--card);
border: 1px solid var(--border);
border-radius: 10px;
margin-bottom: 1.2rem;
overflow: hidden;
}
.step-header {
display: flex;
align-items: center;
gap: .75rem;
padding: .9rem 1.2rem;
border-bottom: 1px solid var(--border);
background: var(--card2);
}
.step-num {
font-size: .72rem;
font-weight: 700;
color: var(--muted);
font-family: monospace;
min-width: 30px;
}
.step-title {
font-size: .95rem;
font-weight: 600;
color: #fff;
flex: 1;
}
.step-body { padding: 1rem 1.2rem; }
/* ---- ITEM ROWS ---- */
.items { width: 100%; border-collapse: collapse; font-size: .83rem; }
.items td {
padding: .45rem .6rem;
border-bottom: 1px solid var(--border);
vertical-align: top;
line-height: 1.45;
}
.items tr:last-child td { border-bottom: none; }
.items td:first-child { color: var(--text); width: 55%; }
.items td:last-child { color: var(--muted); width: 45%; font-size: .78rem; }
.items tr.flag-mustfix td:first-child { color: #f0853a; }
.items tr.flag-done td:first-child { color: var(--green); }
.items tr.flag-todo td:first-child { color: var(--yellow); }
.items tr.flag-open td:first-child { color: var(--blue); }
.items tr.flag-warn td:first-child { color: #da3633; }
/* ---- BADGES ---- */
.badge {
display: inline-block;
border-radius: 4px;
padding: .15rem .4rem;
font-size: .68rem;
font-weight: 700;
text-transform: uppercase;
letter-spacing: .04em;
white-space: nowrap;
margin-left: .3rem;
vertical-align: middle;
}
.badge-ok { background: #1a2e1a; color: var(--green); border: 1px solid var(--green); }
.badge-mustfix { background: #2e1c10; color: #f0853a; border: 1px solid #f0853a; }
.badge-todo { background: #2e250a; color: var(--yellow); border: 1px solid var(--yellow); }
.badge-open { background: #0d1e30; color: var(--blue); border: 1px solid var(--blue); }
.badge-warn { background: #2e0d0c; color: #da3633; border: 1px solid #da3633; }
.badge-future { background: #1e1e2e; color: var(--purple); border: 1px solid var(--purple); }
.badge-new { background: #0d1e30; color: #79c0ff; border: 1px solid #79c0ff; }
/* ---- ROW EXPAND ---- */
.items td.xp { width: 54px; padding: .25rem .3rem; text-align: right; vertical-align: middle; }
.xp-btn {
background: rgba(88,166,255,.08);
border: 1px solid rgba(88,166,255,.25);
color: var(--blue);
cursor: pointer; font-size: .7rem; padding: .15rem .4rem;
border-radius: 4px; line-height: 1.4; transition: background .1s, border-color .1s;
white-space: nowrap;
}
.xp-btn:hover { background: rgba(88,166,255,.18); border-color: var(--blue); }
.xp-btn.open { background: rgba(88,166,255,.2); border-color: var(--blue); }
.items tr.detail-tr td {
background: rgba(88,166,255,.04);
border-bottom: 1px solid var(--border);
padding: .8rem 1rem .8rem 1.2rem;
font-size: .82rem;
color: var(--muted);
}
.detail-heading {
font-size: .7rem; text-transform: uppercase; letter-spacing: .08em;
color: var(--muted); margin-bottom: .4rem;
}
.detail-desc-script {
color: var(--text); line-height: 1.65; font-size: .85rem;
margin-bottom: .6rem; white-space: pre-wrap; word-break: break-word;
}
.detail-desc-script--empty { color: var(--muted); font-style: italic; }
.detail-note {
margin-top: .3rem; margin-bottom: .5rem; color: var(--muted);
font-size: .78rem; font-style: italic; line-height: 1.45;
}
.detail-divider {
border: none; border-top: 1px solid var(--border); margin: .65rem 0;
}
/* row comment thread */
.rcl { margin-bottom: .4rem; }
.rci { padding: .3rem 0; border-bottom: 1px dashed rgba(255,255,255,.06); font-size: .8rem; }
.rci:last-child { border-bottom: none; }
.rci-meta { font-size: .7rem; color: var(--muted); margin-bottom: .1rem; }
.rci-body { color: var(--text); white-space: pre-wrap; word-break: break-word; }
.rcf { display: flex; gap: .35rem; align-items: flex-start; margin-top: .5rem; flex-wrap: wrap; }
.rcf-name {
width: 110px; background: var(--bg); border: 1px solid var(--border);
border-radius: 5px; color: var(--text); font-size: .78rem;
padding: .28rem .45rem; font-family: inherit;
}
.rcf-text {
flex: 1; min-width: 140px; background: var(--bg); border: 1px solid var(--border);
border-radius: 5px; color: var(--text); font-size: .8rem;
padding: .28rem .45rem; font-family: inherit; resize: none; min-height: 36px;
}
.rcf-name:focus, .rcf-text:focus { outline: none; border-color: var(--accent-bright); }
.rcf-send {
background: var(--accent-bright); color: #fff; border: none;
border-radius: 5px; padding: .28rem .6rem; font-size: .78rem;
cursor: pointer; white-space: nowrap; line-height: 1.4;
}
.rcf-send:hover { opacity: .85; }
.rcf-send:disabled { opacity: .35; cursor: default; }
/* ---- NOTES ---- */
.note {
margin-top: .75rem;
padding: .6rem .8rem;
background: rgba(88,166,255,.06);
border-left: 3px solid var(--accent-bright);
border-radius: 0 5px 5px 0;
font-size: .8rem;
color: var(--muted);
line-height: 1.5;
}
.note strong { color: var(--text); }
.note code {
font-family: monospace;
font-size: .78rem;
background: rgba(255,255,255,.07);
padding: .1rem .3rem;
border-radius: 3px;
}
/* ---- STEP FOOTER ---- */
.step-footer {
display: flex;
align-items: center;
justify-content: space-between;
padding: .6rem 1.2rem;
border-top: 1px solid var(--border);
background: var(--card2);
gap: .5rem;
flex-wrap: wrap;
}
.step-footer code {
font-family: monospace;
font-size: .78rem;
background: rgba(255,255,255,.07);
padding: .1rem .3rem;
border-radius: 3px;
color: var(--muted);
}
.step-status { font-size: .78rem; color: var(--muted); }
/* ---- SECTION HEADING ---- */
.section-label {
font-size: .72rem;
text-transform: uppercase;
letter-spacing: .1em;
color: var(--muted);
margin: 1.8rem 0 .7rem;
padding-bottom: .3rem;
border-bottom: 1px solid var(--border);
}
.section-label:first-child { margin-top: 0; }
/* ---- INLINE CODE ---- */
code {
font-family: monospace;
font-size: .82em;
background: rgba(255,255,255,.08);
padding: .1rem .3rem;
border-radius: 3px;
}
/* ---- COMMENT WIDGET ---- */
.comment-widget { width: 100%; }
.comment-toggle {
background: none;
border: 1px dashed var(--border);
border-radius: 5px;
color: var(--muted);
font-size: .8rem;
padding: .25rem .7rem;
cursor: pointer;
transition: border-color .15s, color .15s;
display: flex;
align-items: center;
gap: .4rem;
}
.comment-toggle:hover { border-color: var(--blue); color: var(--blue); }
.comment-toggle .cnt {
background: var(--accent-bright);
color: #fff;
border-radius: 10px;
font-size: .7rem;
padding: .05rem .35rem;
display: none;
}
.comment-toggle .cnt.visible { display: inline; }
.comment-panel {
display: none;
margin-top: .5rem;
border: 1px solid var(--border);
border-radius: 8px;
overflow: hidden;
}
.comment-panel.open { display: block; }
.comment-list { padding: .5rem; }
.comment-item {
padding: .5rem .6rem;
border-bottom: 1px solid var(--border);
font-size: .82rem;
line-height: 1.5;
}
.comment-item:last-child { border-bottom: none; }
.comment-meta {
font-size: .72rem;
color: var(--muted);
margin-bottom: .25rem;
}
.comment-body { color: var(--text); white-space: pre-wrap; word-break: break-word; }
.comment-empty {
padding: .6rem;
font-size: .8rem;
color: var(--muted);
text-align: center;
font-style: italic;
}
.comment-form {
border-top: 1px solid var(--border);
padding: .6rem;
display: flex;
flex-direction: column;
gap: .4rem;
}
.comment-input-name {
width: 100%;
background: var(--bg);
border: 1px solid var(--border);
border-radius: 5px;
color: var(--text);
font-size: .8rem;
padding: .35rem .5rem;
resize: none;
font-family: inherit;
}
.comment-input {
width: 100%;
background: var(--bg);
border: 1px solid var(--border);
border-radius: 5px;
color: var(--text);
font-size: .82rem;
padding: .4rem .5rem;
resize: vertical;
min-height: 60px;
font-family: inherit;
line-height: 1.4;
}
.comment-input:focus, .comment-input-name:focus {
outline: none;
border-color: var(--accent-bright);
}
.comment-submit {
align-self: flex-end;
background: var(--accent-bright);
color: #fff;
border: none;
border-radius: 5px;
padding: .3rem .8rem;
font-size: .8rem;
cursor: pointer;
transition: opacity .15s;
}
.comment-submit:hover { opacity: .85; }
.comment-submit:disabled { opacity: .4; cursor: default; }
.comment-error { font-size: .75rem; color: var(--red); }
footer {
border-top: 1px solid var(--border);
padding: 1.2rem 2rem;
text-align: center;
font-size: .8rem;
color: var(--muted);
margin-top: 3rem;
}
footer a { color: var(--muted); text-decoration: none; }
footer a:hover { color: var(--text); }
@media (max-width: 700px) {
.layout { grid-template-columns: 1fr; }
.sidebar { position: static; }
}
</style>
</head>
<body>
<header>
<div>
<a href="/" style="text-decoration:none;display:flex;align-items:center;gap:.6rem">
<img src="/x9-logo.jpeg" alt="X9.cz" style="height:28px;width:28px;border-radius:5px;object-fit:cover;">
<span class="logo-text">xetup</span><span class="logo-sub">by X9.cz</span>
</a>
</div>
<nav>
<a href="/spec/" class="active">Specifikace</a>
<a href="https://git.xetup.x9.cz/x9/xetup">Git</a>
<a href="https://git.xetup.x9.cz/x9/xetup/issues">Issues</a>
</nav>
</header>
<div class="layout">
<!-- SIDEBAR -->
<aside class="sidebar">
<h4>Kroky (scripty)</h4>
<a href="#step-00">00 &ndash; Admin ucet</a>
<a href="#step-01">01 &ndash; Bloatware</a>
<a href="#step-02">02 &ndash; Software (winget)</a>
<a href="#step-03">03 &ndash; Registry (HKLM)</a>
<a href="#step-04">04 &ndash; Default Profile</a>
<a href="#step-05">05 &ndash; Personalizace</a>
<a href="#step-06">06 &ndash; Scheduled Tasks</a>
<a href="#step-07">07 &ndash; BackInfo</a>
<a href="#step-08">08 &ndash; Aktivace Windows</a>
<hr class="sidebar-divider">
<h4>Planovane kroky</h4>
<a href="#step-pc">09 &ndash; PC identita + C:\X9</a>
<a href="#step-net">10 &ndash; Network discovery</a>
<a href="#step-taskbar">Taskbar profily</a>
<hr class="sidebar-divider">
<h4>Architektura</h4>
<a href="#arch-xetup">xetup.exe (Go TUI)</a>
<a href="#arch-spec">spec.yaml</a>
</aside>
<!-- CONTENT -->
<div class="content">
<h1>Specifikace &amp; anotace</h1>
<div class="meta">
<span>Verze: 0.3-draft</span>
<span>Datum: 2026-04-16</span>
<span><a href="https://git.xetup.x9.cz/x9/xetup">x9/xetup</a></span>
<span>Status: aktivni vyvoj</span>
</div>
<div class="legend">
<span><span class="badge badge-ok">OK</span> hotovo, v provozu</span>
<span><span class="badge badge-mustfix">Must fix</span> zname chyby, nutno opravit</span>
<span><span class="badge badge-todo">TODO</span> naplanovano, nerealizovano</span>
<span><span class="badge badge-open">Open</span> otevrena otazka</span>
<span><span class="badge badge-warn">Warn</span> potencialni problem</span>
<span><span class="badge badge-new">New</span> nova feature</span>
<span><span class="badge badge-future">Future</span> dlouhodoby plan</span>
</div>
<!-- ============================================================ -->
<p class="section-label">Stavajici kroky (scripty)</p>
<!-- STEP 00 -->
<div class="step" id="step-00">
<div class="step-header">
<span class="step-num">00</span>
<span class="step-title">Admin ucet (adminx9)</span>
<span class="badge badge-mustfix">Must fix</span>
</div>
<div class="step-body">
<table class="items">
<tr class="flag-done"><td>Vytvorit lokalni ucet <code>adminx9</code></td><td>Hotovo</td></tr>
<tr class="flag-done"><td>Pridat do skupiny Administrators</td><td>Hotovo</td></tr>
<tr class="flag-done"><td>Skryt z login obrazovky (SpecialAccounts\UserList = 0)</td><td>Hotovo</td></tr>
<tr class="flag-done"><td>Heslo nevypirsi, uzivatel nesmeni heslo</td><td>Hotovo</td></tr>
<tr class="flag-mustfix"><td>Zadne heslo (aktualne nastavovano z config.json)</td><td>Zmenit: ucet BEZ hesla (rozhodnuti)</td></tr>
<tr class="flag-mustfix"><td>FullName = "X9.cz s.r.o." (via ADSI)</td><td>Chybi, doplnit</td></tr>
</table>
<div class="note">
<strong>Proc bez hesla:</strong> Ucet je skryty pred uzivateli, slouzi pouze MSP adminstraci.
Heslo v config.json by bylo ulozene citelne.
</div>
</div>
<div class="step-footer">
<span class="step-status">Script: <code>00-admin-account.ps1</code></span>
<div class="comment-widget" data-issue="1"></div>
</div>
</div>
<!-- STEP 01 -->
<div class="step" id="step-01">
<div class="step-header">
<span class="step-num">01</span>
<span class="step-title">Bloatware removal</span>
<span class="badge badge-ok">OK</span>
</div>
<div class="step-body">
<table class="items">
<tr class="flag-done"><td>AppX balicky &ndash; odstraneni pro vsechny uzivatele a provisioned</td><td>Remove-AppxPackage -AllUsers + Remove-AppxProvisionedPackage</td></tr>
<tr class="flag-done"><td>Zachovano: Microsoft.WindowsCalculator</td><td>Zamerny vyjimek</td></tr>
<tr class="flag-done"><td>Windows Capabilities (Fax, IE, OpenSSH, WMP, WordPad, …)</td><td>Remove-WindowsCapability</td></tr>
<tr class="flag-done"><td>Windows Optional Features (PS 2.0, MediaPlayback, Recall, …)</td><td>Disable-WindowsOptionalFeature</td></tr>
<tr class="flag-warn"><td>Microsoft-RemoteDesktopConnection NESMI byt odstranen</td><td>RDP klient musi zustat funkci. Overit ze neni v seznamu.</td></tr>
<tr class="flag-warn"><td>OneDrive nesmi byt odstranovano tady</td><td>OneDrive musi zustat instalovatelny pro M365.</td></tr>
</table>
</div>
<div class="step-footer">
<span class="step-status">Script: <code>01-bloatware.ps1</code></span>
<div class="comment-widget" data-issue="2"></div>
</div>
</div>
<!-- STEP 02 -->
<div class="step" id="step-02">
<div class="step-header">
<span class="step-num">02</span>
<span class="step-title">Software (winget)</span>
<span class="badge badge-todo">TODO</span>
</div>
<div class="step-body">
<table class="items">
<tr class="flag-done"><td>7-Zip (<code>7zip.7zip</code>)</td><td>OK</td></tr>
<tr class="flag-done"><td>Adobe Acrobat Reader 64-bit (<code>Adobe.Acrobat.Reader.64-bit</code>)</td><td>OK</td></tr>
<tr class="flag-done"><td>OpenVPN Connect (<code>OpenVPNTechnologies.OpenVPNConnect</code>)</td><td>OK</td></tr>
<tr class="flag-todo"><td>Seznam SW je neuplny &ndash; co dalsiho patri dovnitr?</td><td>TODO: doplnit uplny seznam</td></tr>
<tr class="flag-done"><td>Atera Agent install</td><td>Invoke-WebRequest + <code>msiexec /i /qn</code></td></tr>
<tr class="flag-done"><td>Adobe PDF default: .pdf -&gt; AcroRd32 po instalaci</td><td>OK &ndash; UCPD stop/start kolem zápisu asociace</td></tr>
<tr class="flag-done"><td>UCPD.sys (kernel driver, od Feb 2024) blokuje UserChoice</td><td>Reseno: Stop-Service ucpd &rarr; HKCR zapis &rarr; Start-Service ucpd</td></tr>
</table>
<div class="note">
<strong>Atera Agent URL:</strong><br>
<code>https://x9.servicedesk.atera.com/api/utils/agent-install/windows/?cid=31&amp;aeid=50b72e7113e54a63ac76b96c54c7e337</code>
</div>
</div>
<div class="step-footer">
<span class="step-status">Script: <code>02-software.ps1</code></span>
<div class="comment-widget" data-issue="3"></div>
</div>
</div>
<!-- STEP 03 -->
<div class="step" id="step-03">
<div class="step-header">
<span class="step-num">03</span>
<span class="step-title">System Registry (HKLM)</span>
<span class="badge badge-todo">TODO</span>
</div>
<div class="step-body">
<table class="items">
<tr class="flag-done"><td>Bypass NRO (OOBE\BypassNRO = 1)</td><td>OK</td></tr>
<tr class="flag-done"><td>Zakaz auto-instalace Teams</td><td>ConfigureChatAutoInstall = 0</td></tr>
<tr class="flag-done"><td>Zakaz Cloud Optimized Content</td><td>OK</td></tr>
<tr class="flag-done"><td>Zakaz Widgets / News and Interests</td><td>OK</td></tr>
<tr class="flag-done"><td>Hesla bez expirace (<code>net accounts /maxpwage:UNLIMITED</code>)</td><td>OK</td></tr>
<tr class="flag-done"><td>Casova zona: Central Europe Standard Time</td><td>OK</td></tr>
<tr class="flag-done"><td>Zakaz GameDVR</td><td>OK</td></tr>
<tr class="flag-done"><td>Edge &ndash; skryt First Run Experience</td><td>HKLM\Policies\Edge\HideFirstRunExperience = 1</td></tr>
<tr class="flag-done"><td>OneDrive uninstall (intentional)</td><td>OneDriveSetup.exe /uninstall &ndash; odstrani pre-installed verzi. M365 si nainstaluje vlastni.</td></tr>
<tr class="flag-todo"><td>Edge policies &ndash; doplnit ~15 dalsich klicu</td><td>Viz seznam nize</td></tr>
<tr class="flag-todo"><td>Powercfg nastaveni (spotreba energie)</td><td>Pridat: standby-ac 0, monitor-ac 60, standby-dc 30, monitor-dc 15</td></tr>
<tr class="flag-todo"><td>Proxy auto-detect zakaz (AutoDetect = 0)</td><td>HKLM\SOFTWARE\Policies\Microsoft\Windows\CurrentVersion\Internet Settings</td></tr>
</table>
<div class="note">
<strong>Edge policies k doplneni:</strong>
DefaultBrowserSettingEnabled = 0, NewTabPageContentEnabled = 0,
ImportOnEachLaunch = 0, ShowRecommendationsEnabled = 0,
PersonalizationReportingEnabled = 0, SpotlightExperiencesAndRecommendationsEnabled = 0,
DiagnosticData = 0, EdgeShoppingAssistantEnabled = 0, EdgeCollectionsEnabled = 0,
HubsSidebarEnabled = 0, ShowMicrosoftRewards = 0, SearchSuggestEnabled = 0 a dalsi.
</div>
<div class="note">
<strong>Powercfg prikazy:</strong><br>
<code>powercfg /change standby-timeout-ac 0</code> (neusne na nabijeni)<br>
<code>powercfg /change monitor-timeout-ac 60</code> (monitor zhasne po 60 min)<br>
<code>powercfg /change standby-timeout-dc 30</code><br>
<code>powercfg /change monitor-timeout-dc 15</code>
</div>
</div>
<div class="step-footer">
<span class="step-status">Script: <code>03-system-registry.ps1</code></span>
<div class="comment-widget" data-issue="4"></div>
</div>
</div>
<!-- STEP 04 -->
<div class="step" id="step-04">
<div class="step-header">
<span class="step-num">04</span>
<span class="step-title">Default Profile (NTUSER.DAT)</span>
<span class="badge badge-ok">OK</span>
</div>
<div class="step-body">
<table class="items">
<tr class="flag-done"><td>Taskbar: zarovnat vlevo (TaskbarAl = 0)</td><td>Win11 default je center</td></tr>
<tr class="flag-done"><td>Taskbar: skryt Search, Copilot, Task View, Widgets, Chat</td><td>OK</td></tr>
<tr class="flag-done"><td>Taskbar: zobrazit vsechny ikonky v tray (Scheduled task)</td><td>ShowAllTrayIcons</td></tr>
<tr class="flag-done"><td>Taskbar: vyprazdnit pinlist (TaskbarLayoutModification.xml)</td><td>OK</td></tr>
<tr class="flag-done"><td>Explorer: zobrazovat pripony souboru (HideFileExt = 0)</td><td>OK</td></tr>
<tr class="flag-done"><td>Explorer: otevrit na This PC (LaunchTo = 1)</td><td>OK</td></tr>
<tr class="flag-done"><td>Start menu: vyprazdnit piny (Win11)</td><td>ConfigureStartPins = {"pinnedList":[]}</td></tr>
<tr class="flag-done"><td>Start menu: zakaz Bing vyhledavani</td><td>DisableSearchBoxSuggestions = 1</td></tr>
<tr class="flag-done"><td>Copilot: zakaz (TurnOffWindowsCopilot = 1)</td><td>OK</td></tr>
<tr class="flag-done"><td>NumLock zapnout pri startu (InitialKeyboardIndicators = 2)</td><td>OK</td></tr>
<tr class="flag-done"><td>Accent barva na titulnich listech (ColorPrevalence = 1)</td><td>OK</td></tr>
<tr class="flag-done"><td>OneDrive RunOnce klic je tady &ndash; smazat</td><td>Opraveno &ndash; blok odstranen ze scriptu (brani reinstalaci pres M365)</td></tr>
<tr class="flag-done"><td>Explorer: ShowRecent = 0, ShowFrequent = 0</td><td>Skryt nedavne a caste soubory v Quick Access</td></tr>
<tr class="flag-done"><td>Explorer: FullPath = 1 (CabinetState)</td><td>Zobrazovat plnou cestu v titulku okna Explorera</td></tr>
</table>
<div class="note">
<strong>Metoda:</strong> <code>reg load HKU\DefaultProfile C:\Users\Default\NTUSER.DAT</code>
&rarr; zapsat zmeny &rarr; <code>reg unload HKU\DefaultProfile</code>.<br>
Tato operace musi probihat PRED prvnim prihlasenim uzivatele.
Aktualne prihlaseny uzivatel dostava zmeny pres primy zapis do HKCU.
</div>
</div>
<div class="step-footer">
<span class="step-status">Script: <code>04-default-profile.ps1</code></span>
<div class="comment-widget" data-issue="5"></div>
</div>
</div>
<!-- STEP 05 -->
<div class="step" id="step-05">
<div class="step-header">
<span class="step-num">05</span>
<span class="step-title">Personalizace (barvy, tapeta)</span>
<span class="badge badge-ok">OK</span>
</div>
<div class="step-body">
<table class="items">
<tr class="flag-done"><td>System tema (taskbar, Start): Dark</td><td>OK</td></tr>
<tr class="flag-done"><td>Aplikacni tema: Light</td><td>OK</td></tr>
<tr class="flag-done"><td>Accent barva: #223B47 (tmave modroseda)</td><td>OK</td></tr>
<tr class="flag-done"><td>Accent barva na Start a taskbaru: ano</td><td>OK</td></tr>
<tr class="flag-done"><td>Pruhlednost: vypnuta</td><td>OK</td></tr>
<tr class="flag-done"><td>Tapeta: jednobarevna #223B47 (bez obrazku)</td><td>BackInfo prepise tapetu svym BMP</td></tr>
</table>
<div class="note">
BackInfo.exe (STEP 07) prepise tapetu BMP se systemovymi informacemi.
Jednobarevna tapeta je fallback pro pripad, ze BackInfo nedobehne nebo se nespusti.
</div>
</div>
<div class="step-footer">
<span class="step-status">Script: <code>05-personalization.ps1</code></span>
<div class="comment-widget" data-issue="6"></div>
</div>
</div>
<!-- STEP 06 -->
<div class="step" id="step-06">
<div class="step-header">
<span class="step-num">06</span>
<span class="step-title">Scheduled Tasks</span>
<span class="badge badge-ok">OK</span>
</div>
<div class="step-body">
<table class="items">
<tr class="flag-done"><td>ShowAllTrayIcons &ndash; pri logonu + kazdou 1 min</td><td>Win11 automaticky skryva tray ikony</td></tr>
<tr class="flag-done"><td>UnlockStartLayout &ndash; jednou po aplikaci layoutu</td><td>Odemkne Start menu pro uzivatelske zmeny</td></tr>
<tr class="flag-done"><td>PDF-DefaultApp pri kazdem logonu &ndash; odstranen</td><td>PDF asociace nastavena jednou v kroku 02 (UCPD stop/start). Task nebyl nutny.</td></tr>
</table>
</div>
<div class="step-footer">
<span class="step-status">Script: <code>06-scheduled-tasks.ps1</code></span>
<div class="comment-widget" data-issue="7"></div>
</div>
</div>
<!-- STEP 07 -->
<div class="step" id="step-07">
<div class="step-header">
<span class="step-num">07</span>
<span class="step-title">BackInfo (systemovy info na tapete)</span>
<span class="badge badge-mustfix">Must fix</span>
</div>
<div class="step-body">
<table class="items">
<tr class="flag-mustfix"><td><code>07-desktop-info.ps1</code> SMAZAT &ndash; stary pristup</td><td>Nahradit deploym. krokem pro BackInfo.exe</td></tr>
<tr class="flag-mustfix"><td>Zkopirovat <code>assets/Backinfo/</code> do <code>C:\Program Files\Backinfo\</code></td><td>Pridat do master scriptu</td></tr>
<tr class="flag-mustfix"><td>Spustit <code>backinfo_W11.ps1</code> (detekce OS, registry, Startup)</td><td>Pridat do master scriptu</td></tr>
<tr class="flag-done"><td>BackInfo.exe v assets/Backinfo/ k dispozici</td><td>Hotovo &ndash; jen deploy krok chybi</td></tr>
<tr class="flag-done"><td>BackInfo auto-start pri kazdem logonu via Startup shortcut</td><td>Zaridi backinfo_W11.ps1</td></tr>
</table>
<div class="note">
<strong>BackInfo.ini konfiguruje:</strong> hostname (velky, centrovan), uzivatelske jmeno,
OS verze, HW info (CPU, RAM, disk), sitove informace (IP, hostname).<br><br>
<strong>Proc BackInfo misto vlastniho PS:</strong>
BackInfo.exe podporuje Win10 i Win11 bez specialnich hacku, je stabilni a uz je v assets.
</div>
</div>
<div class="step-footer">
<span class="step-status">Script: <code>07-desktop-info.ps1</code> (ke smazani) &rarr; novy inline krok v Deploy-Windows.ps1</span>
<div class="comment-widget" data-issue="8"></div>
</div>
</div>
<!-- STEP 08 -->
<div class="step" id="step-08">
<div class="step-header">
<span class="step-num">08</span>
<span class="step-title">Windows aktivace</span>
<span class="badge badge-ok">OK</span>
<span class="badge badge-open">Open</span>
</div>
<div class="step-body">
<table class="items">
<tr class="flag-done"><td>OA3 BIOS/UEFI klic &ndash; kontrola embedded key</td><td>WMI: SoftwareLicensingService.OA3xOriginalProductKey</td></tr>
<tr class="flag-done"><td>Klic z config.json (<code>activation.productKey</code>)</td><td>OK &ndash; priorita nad OA3 a GVLK</td></tr>
<tr class="flag-done"><td>Fallback na GVLK (KMS client key) dle edice OS</td><td>OK</td></tr>
<tr class="flag-done"><td>Volitelny KMS server (<code>activation.kmsServer</code>)</td><td>OK</td></tr>
<tr class="flag-done"><td>Preskocit pokud jiz aktivovano</td><td>OK</td></tr>
<tr class="flag-open"><td>Typ klice: MAK vs KMS vs retail?</td><td>Zavisi na klientovi &ndash; otevrena otazka</td></tr>
</table>
</div>
<div class="step-footer">
<span class="step-status">Script: <code>08-activation.ps1</code></span>
<div class="comment-widget" data-issue="9"></div>
</div>
</div>
<!-- ============================================================ -->
<p class="section-label">Nove kroky (planovane)</p>
<!-- STEP 09 -->
<div class="step" id="step-pc">
<div class="step-header">
<span class="step-num">09</span>
<span class="step-title">PC identita &ndash; Rename + C:\X9</span>
<span class="badge badge-new">New</span>
</div>
<div class="step-body">
<table class="items">
<tr class="flag-todo"><td>Rename-Computer dle parametru z TUI nebo config.json</td><td>Finalni krok pred restartem &ndash; PC name + popis</td></tr>
<tr class="flag-todo"><td>Nastavit popis pocitace (Computer Description)</td><td>Via WMI nebo registry HKLM\SYSTEM\...\ComputerName</td></tr>
<tr class="flag-todo"><td>Vytvorit <code>C:\X9\</code> adresarovou strukturu</td><td>Pro logy, skripty, assets</td></tr>
<tr class="flag-todo"><td>Vlastni ikonka pro <code>C:\X9\</code> slozku</td><td>Desktop.ini + X9-ikona.ico</td></tr>
</table>
<div class="note">
Rename-Computer vyzaduje restart. Tento krok musi byt posledni pred finalnim shrnutim.
Technik vi, ze po deployi nasleduje restart.
</div>
</div>
<div class="step-footer">
<span class="step-status">Script: novy <code>09-pc-identity.ps1</code></span>
<div class="comment-widget" data-issue="12"></div>
</div>
</div>
<!-- STEP 10 -->
<div class="step" id="step-net">
<div class="step-header">
<span class="step-num">10</span>
<span class="step-title">Network discovery + firewall</span>
<span class="badge badge-new">New</span>
</div>
<div class="step-body">
<table class="items">
<tr class="flag-todo"><td>Nastavit sitovy profil jako Private (ne Public)</td><td><code>Set-NetConnectionProfile -NetworkCategory Private</code></td></tr>
<tr class="flag-todo"><td>Povolit ping (ICMP) pro diagnostiku</td><td>Firewall rule: Enable ICMPv4/ICMPv6</td></tr>
<tr class="flag-todo"><td>Zapnout Network Discovery pro Private profil</td><td><code>netsh advfirewall</code> nebo <code>Set-NetFirewallRule</code></td></tr>
</table>
<div class="note">
Pozor: Sitovy profil (Private/Public) se muze zmenit po kazdem prihlaseni k jine siti.
Zvazit scheduled task pri logonu pro opakovanou korekci profilu.
</div>
</div>
<div class="step-footer">
<span class="step-status">Script: novy <code>10-network.ps1</code></span>
<div class="comment-widget" data-issue="10"></div>
</div>
</div>
<!-- TASKBAR -->
<div class="step" id="step-taskbar">
<div class="step-header">
<span class="step-num">---</span>
<span class="step-title">Taskbar pinned apps (profily)</span>
<span class="badge badge-new">New</span>
<span class="badge badge-future">Future</span>
</div>
<div class="step-body">
<table class="items">
<tr class="flag-todo"><td><code>-ProfileType</code> parametr: admin vs user varianta</td><td>Ruzna sada pinnutych appek dle role uzivatele</td></tr>
<tr class="flag-todo"><td>XML layout pro "admin": Explorer, PS, Edge, Notepad++, …</td><td>TaskbarLayoutModification.xml</td></tr>
<tr class="flag-todo"><td>XML layout pro "user": Edge, Outlook, Teams, Explorer, …</td><td>Odlisna sada pro bezneho zamestnance</td></tr>
</table>
<div class="note">
Win11 24H2 zmenil zpusob aplikace Taskbar layoutu (ProvisionedLayoutModification.xml vs. starsi TaskbarLayoutModification.xml).
Nutno overit kompatibilitu s ruznymy buildy pred implementaci.
</div>
</div>
<div class="step-footer">
<span class="step-status">Zacleneni: STEP 04 nebo vlastni script</span>
<div class="comment-widget" data-issue="13"></div>
</div>
</div>
<!-- ============================================================ -->
<p class="section-label">Architektura (budoucnost)</p>
<!-- ARCH XETUP -->
<div class="step" id="arch-xetup">
<div class="step-header">
<span class="step-num">Arc</span>
<span class="step-title">xetup.exe &ndash; Go TUI launcher</span>
<span class="badge badge-future">Future</span>
</div>
<div class="step-body">
<table class="items">
<tr class="flag-todo"><td>Single binary (go:embed scripty + assets)</td><td>Offline provoz, jedna stazitelna .exe</td></tr>
<tr class="flag-todo"><td>TUI form (huh/bubbletea): PC name, popis, product key</td><td>Interaktivni zadani dat technikem</td></tr>
<tr class="flag-todo"><td>Checklist kroku (on/off per-script) + ulozit do config.json</td><td>Opakovatelne nasazeni u stejneho klienta</td></tr>
<tr class="flag-todo"><td>Live log output behem spousteni PS scriptu</td><td>Stdout z powershell.exe v realnem case</td></tr>
<tr class="flag-todo"><td>Finalni summary OK/ERROR</td><td>Na konci nasazeni</td></tr>
<tr class="flag-todo"><td>Self-update: stahnout novou verzi z xetup.x9.cz</td><td>Overit hash pred spustenim</td></tr>
<tr class="flag-future"><td>config.json: per-klient preset (prefix jmena PC, SW, klic)</td><td>Lezi vedle .exe na USB klienta</td></tr>
<tr class="flag-future"><td>OpenVPN soubor + doménovy join + domén. uzivatel pro profil</td><td>Rozsireni TUI formulare v budoucnu</td></tr>
</table>
<div class="note">
<strong>Struktura:</strong> <code>cmd/xetup/</code>, <code>internal/config/</code>,
<code>internal/spec/</code>, <code>internal/tui/</code>, <code>internal/runner/</code><br><br>
<strong>Go zavislosti:</strong>
bubbletea (TUI framework), huh (forms), lipgloss (styling)
</div>
</div>
<div class="step-footer">
<span class="step-status">Status: navrh, zatim zadny kod</span>
<div class="comment-widget" data-issue="11"></div>
</div>
</div>
<!-- ARCH SPEC -->
<div class="step" id="arch-spec">
<div class="step-header">
<span class="step-num">Arc</span>
<span class="step-title">spec.yaml &ndash; single source of truth</span>
<span class="badge badge-future">Future</span>
</div>
<div class="step-body">
<table class="items">
<tr class="flag-todo"><td>Popis vsech kroku: id, label, script, default</td><td>xetup.exe cte spec.yaml pro TUI checklist</td></tr>
<tr class="flag-todo"><td>Pole "requires" (napr. activation vyzaduje productKey)</td><td>TUI upozorni pokud chybi</td></tr>
<tr class="flag-future"><td>Auto-generovana dokumentace z spec.yaml</td><td>CI akce: spec.yaml &rarr; tato stranka</td></tr>
<tr class="flag-future"><td>spec.yaml jako SSOT pro tuto stranku i deploy skripty</td><td>Idealni stav: stranka vzdy odpovida kodu</td></tr>
</table>
<div class="note">
<strong>Navrh struktury spec.yaml:</strong><br>
<code style="display:block; white-space:pre; line-height:1.6">steps:
- id: admin-account
label: "Admin account (adminx9)"
script: 00-admin-account.ps1
default: true
- id: activation
label: "Windows activation"
script: 08-activation.ps1
default: true
requires: [productKey]</code>
</div>
</div>
<div class="step-footer">
<span class="step-status">Status: navrh, zatim zadny soubor</span>
<div class="comment-widget" data-issue="14"></div>
</div>
</div>
</div><!-- /content -->
</div><!-- /layout -->
<footer>
&copy; 2026 <a href="https://x9.cz">X9.cz s.r.o.</a>
&nbsp;&middot;&nbsp;
<a href="https://git.xetup.x9.cz/x9/xetup">Forgejo</a>
&nbsp;&middot;&nbsp;
<a href="/">xetup.x9.cz</a>
</footer>
<script>
(function() {
const API = '/forgejo-api';
const REPO = 'x9/xetup';
const TOKEN = 'e67f674af71847c4349b79b51d2b66a1ea41d031';
const HEADS = { 'Authorization': 'token ' + TOKEN, 'Content-Type': 'application/json' };
function timeAgo(iso) {
const s = Math.floor((Date.now() - new Date(iso)) / 1000);
if (s < 60) return 'prave ted';
if (s < 3600) return Math.floor(s/60) + ' min';
if (s < 86400)return Math.floor(s/3600) + ' hod';
return Math.floor(s/86400) + ' dni';
}
function renderComments(list, listEl) {
listEl.innerHTML = '';
if (!list.length) {
listEl.innerHTML = '<div class="comment-empty">Zatim zadne komentare. Bud prvni.</div>';
return;
}
list.forEach(c => {
const el = document.createElement('div');
el.className = 'comment-item';
const name = c.user.login === 'xetup-bot'
? (c.body.match(/^\*\*(.+?)\*\*/) ? c.body.match(/^\*\*(.+?)\*\*/)[1] : 'anon')
: c.user.login;
const body = c.body.replace(/^\*\*.+?\*\*\n/, '');
el.innerHTML =
'<div class="comment-meta">' + name + ' &middot; ' + timeAgo(c.created_at) + '</div>' +
'<div class="comment-body">' + body.replace(/</g,'&lt;').replace(/>/g,'&gt;') + '</div>';
listEl.appendChild(el);
});
}
function initWidget(el) {
const issue = el.dataset.issue;
if (!issue || issue === '0') return;
el.innerHTML =
'<button class="comment-toggle" title="Komentare k teto sekci">' +
'&#128172; Komentare <span class="cnt"></span>' +
'</button>' +
'<div class="comment-panel">' +
'<div class="comment-list"><div class="comment-empty">Nacitam...</div></div>' +
'<div class="comment-form">' +
'<input class="comment-input-name" placeholder="Tvoje jmeno (volitelne)" maxlength="40">' +
'<textarea class="comment-input" placeholder="Napiste komentar..."></textarea>' +
'<div style="display:flex;gap:.5rem;align-items:center;justify-content:flex-end">' +
'<span class="comment-error"></span>' +
'<button class="comment-submit">Odeslat</button>' +
'</div>' +
'</div>' +
'</div>';
const toggle = el.querySelector('.comment-toggle');
const panel = el.querySelector('.comment-panel');
const listEl = el.querySelector('.comment-list');
const nameEl = el.querySelector('.comment-input-name');
const textEl = el.querySelector('.comment-input');
const submitEl= el.querySelector('.comment-submit');
const errEl = el.querySelector('.comment-error');
const cntEl = el.querySelector('.cnt');
let loaded = false;
let comments = [];
function load() {
fetch(API + '/repos/' + REPO + '/issues/' + issue + '/comments', { headers: HEADS })
.then(r => r.json())
.then(data => {
comments = Array.isArray(data) ? data : [];
renderComments(comments, listEl);
if (comments.length) {
cntEl.textContent = comments.length;
cntEl.classList.add('visible');
}
loaded = true;
})
.catch(() => { listEl.innerHTML = '<div class="comment-empty">Chyba nacitani.</div>'; });
}
toggle.addEventListener('click', () => {
const open = panel.classList.toggle('open');
if (open && !loaded) load();
});
submitEl.addEventListener('click', () => {
const text = textEl.value.trim();
if (!text) { errEl.textContent = 'Komentar nesmi byt prazdny.'; return; }
errEl.textContent = '';
submitEl.disabled = true;
submitEl.textContent = 'Odesilam...';
const name = nameEl.value.trim() || 'anon';
const body = '**' + name + '**\n' + text;
fetch(API + '/repos/' + REPO + '/issues/' + issue + '/comments', {
method: 'POST', headers: HEADS, body: JSON.stringify({ body: body })
})
.then(r => { if (!r.ok) throw new Error(r.status); return r.json(); })
.then(c => {
comments.push(c);
renderComments(comments, listEl);
cntEl.textContent = comments.length;
cntEl.classList.add('visible');
textEl.value = '';
nameEl.value = '';
submitEl.disabled = false;
submitEl.textContent = 'Odeslat';
})
.catch(() => {
errEl.textContent = 'Chyba odeslani. Zkus znovu.';
submitEl.disabled = false;
submitEl.textContent = 'Odeslat';
});
});
}
document.querySelectorAll('.comment-widget').forEach(initWidget);
// ---- DESCRIPTIONS FROM scripts/*.ps1 headers ----
let DESCRIPTIONS = {};
fetch('/data/descriptions.json')
.then(r => r.json())
.then(d => { DESCRIPTIONS = d; })
.catch(() => {});
// Map spec step id -> script id
const STEP_SCRIPT = {
'step-00': '00-admin-account',
'step-01': '01-bloatware',
'step-02': '02-software',
'step-03': '03-system-registry',
'step-04': '04-default-profile',
'step-05': '05-personalization',
'step-06': '06-scheduled-tasks',
'step-07': '07-desktop-info',
'step-08': '08-activation',
};
function getItemDesc(stepId, slug) {
const scriptId = STEP_SCRIPT[stepId];
if (!scriptId || !DESCRIPTIONS[scriptId]) return null;
return DESCRIPTIONS[scriptId].items[slug] || null;
}
// ---- ROW-LEVEL EXPAND + COMMENTS ----
function rSlug(txt) {
return txt.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '').substring(0, 40);
}
function esc(s) { return s.replace(/</g,'&lt;').replace(/>/g,'&gt;'); }
function renderRowComments(all, slug, listEl) {
const prefix = '[row:' + slug + ']';
const relevant = all.filter(c => c.body.startsWith(prefix));
listEl.innerHTML = '';
if (!relevant.length) {
listEl.innerHTML = '<div style="font-size:.75rem;color:var(--muted);font-style:italic;padding:.2rem 0">Zatim zadne komentare.</div>';
return;
}
relevant.forEach(c => {
const body = c.body.slice(prefix.length + 1);
const m = body.match(/^\*\*(.+?)\*\*\n([\s\S]*)$/);
const name = m ? m[1] : 'anon';
const text = m ? m[2] : body;
const el = document.createElement('div');
el.className = 'rci';
el.innerHTML = '<div class="rci-meta">' + esc(name) + ' &middot; ' + timeAgo(c.created_at) + '</div>' +
'<div class="rci-body">' + esc(text) + '</div>';
listEl.appendChild(el);
});
}
function enhanceRows() {
document.querySelectorAll('.step').forEach(step => {
const widget = step.querySelector('.comment-widget');
const issueNum = widget ? widget.dataset.issue : null;
const stepId = step.id;
step.querySelectorAll('.items tr:not(.detail-tr)').forEach(tr => {
const cells = tr.querySelectorAll('td');
if (!cells.length) return;
const rowText = cells[0].textContent.trim();
const noteText = cells[1] ? cells[1].textContent.trim() : '';
const slug = rSlug(rowText);
// Expand button as last cell
const xpCell = document.createElement('td');
xpCell.className = 'xp';
const btn = document.createElement('button');
btn.className = 'xp-btn';
btn.innerHTML = 'detail ▸';
btn.title = 'Zobrazit detail a komentare';
xpCell.appendChild(btn);
tr.appendChild(xpCell);
// Detail row
const dtr = document.createElement('tr');
dtr.className = 'detail-tr';
dtr.style.display = 'none';
const dtd = document.createElement('td');
dtd.colSpan = 3;
// Description from script header (loaded async - may not be ready yet,
// will be filled in when detail opens)
const scriptDesc = () => getItemDesc(stepId, slug);
dtd.innerHTML =
// description from script header
'<div class="detail-heading">Co to dela</div>' +
'<div class="detail-desc-script"></div>' +
// brief note from col 2 as secondary context
(noteText ? '<div class="detail-note">' + esc(noteText) + '</div>' : '') +
'<hr class="detail-divider">' +
// comments
'<div class="detail-heading">Komentare</div>' +
'<div class="rcl"></div>' +
'<div class="rcf">' +
'<input class="rcf-name" placeholder="Jmeno" maxlength="40">' +
'<textarea class="rcf-text" placeholder="Komentar k tomuto radku..." rows="2"></textarea>' +
'<button class="rcf-send">Odeslat</button>' +
'</div>';
dtr.appendChild(dtd);
tr.after(dtr);
let loaded = false;
let allComments = [];
const getAll = () => allComments;
btn.addEventListener('click', () => {
const opening = dtr.style.display === 'none';
dtr.style.display = opening ? '' : 'none';
btn.classList.toggle('open', opening);
btn.innerHTML = opening ? 'detail ▾' : 'detail ▸';
if (opening) {
// Fill script description (JSON may now be loaded)
const descEl = dtd.querySelector('.detail-desc-script');
const desc = scriptDesc();
if (desc) {
descEl.textContent = desc;
descEl.className = 'detail-desc-script';
} else {
descEl.textContent = 'Popis bude dostupny po spusteni tools/extract-docs.py.';
descEl.className = 'detail-desc-script detail-desc-script--empty';
}
}
if (opening && !loaded && issueNum) {
loaded = true;
const listEl = dtd.querySelector('.rcl');
listEl.innerHTML = '<div style="font-size:.75rem;color:var(--muted)">Nacitam...</div>';
fetch(API + '/repos/' + REPO + '/issues/' + issueNum + '/comments', { headers: HEADS })
.then(r => r.json())
.then(data => {
allComments = Array.isArray(data) ? data : [];
renderRowComments(allComments, slug, listEl);
})
.catch(() => {
listEl.innerHTML = '<div style="font-size:.75rem;color:var(--red)">Chyba nacitani.</div>';
});
}
});
dtd.querySelector('.rcf-send').addEventListener('click', function() {
const nameEl = dtd.querySelector('.rcf-name');
const textEl = dtd.querySelector('.rcf-text');
const text = textEl.value.trim();
if (!text || !issueNum) return;
this.disabled = true;
const name = nameEl.value.trim() || 'anon';
const body = '[row:' + slug + ']\n**' + name + '**\n' + text;
fetch(API + '/repos/' + REPO + '/issues/' + issueNum + '/comments', {
method: 'POST', headers: HEADS, body: JSON.stringify({ body: body })
})
.then(r => r.json())
.then(c => {
allComments.push(c);
renderRowComments(allComments, slug, dtd.querySelector('.rcl'));
textEl.value = ''; nameEl.value = '';
this.disabled = false;
})
.catch(() => { this.disabled = false; });
});
});
});
}
enhanceRows();
})();
</script>
</body>
</html>