T15: Docs viewer sidebar layout (25%/75% grid) sa HTMX fragmentima

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
djuka 2026-02-20 12:55:05 +00:00
parent 70e2ee684f
commit 0e6d0ecd66
7 changed files with 233 additions and 22 deletions

View File

@ -0,0 +1,50 @@
# T15 Izveštaj: Fix — docs viewer zauzima pola ekrana
**Agent:** coder
**Model:** Opus
**Datum:** 2026-02-20
---
## Šta je urađeno
Docs viewer pretvoren u sidebar + content layout (25%/75% grid).
### Izmenjeni fajlovi
| Fajl | Izmena |
|------|--------|
| `web/static/style.css` | docs-layout grid (25%/75%), docs-sidebar, docs-main, responsive |
| `web/templates/docs-list.html` | Sidebar + content layout sa placeholder |
| `web/templates/docs-view.html` | Sidebar sa file listom + content sa breadcrumbs |
| `internal/server/docs.go` | Files polje u docsViewData, HTMX fragment detekcija |
| `internal/server/server_test.go` | 3 nova testa |
### Layout
```
┌──────────────┬───────────────────────────┐
│ Sidebar 25% │ Content 75% │
│ File list │ Breadcrumbs + Markdown │
│ │ │
│ │ │
└──────────────┴───────────────────────────┘
```
- HTMX klik na fajl → swap samo content div (fragment)
- Direktan URL pristup → full page sa sidebar
- Responsive: na <700px 1 kolona (100%)
- min-height: 80vh
### Novi testovi — 3 PASS
```
TestDocsView_HasSidebarLayout PASS
TestDocsView_HTMXReturnsFragment PASS
TestDocsList_HasSidebarLayout PASS
```
### Ukupno projekat: 119 testova, svi prolaze
- `go vet ./...` — čist
- `go build ./...` — prolazi

53
TASKS/review/T15.md Normal file
View File

@ -0,0 +1,53 @@
# T15: Fix — docs viewer zauzima pola ekrana
**Kreirao:** planer
**Datum:** 2026-02-20
**Agent:** coder
**Model:** Sonnet
**Zavisi od:** T12 ✅
---
## Opis
BUG/UI: Docs viewer je premali. Treba da zauzima pola ekrana (50%ширine).
## Izmena
Layout kad je docs otvoren:
```
┌──────────────────────┬──────────────────────┐
│ │ │
│ Lista fajlova │ Sadržaj .md fajla │
│ (sidebar 25%) │ (content 75%) │
│ │ │
│ │ │
│ │ │
│ │ │
└──────────────────────┴──────────────────────┘
```
Ceo docs tab zauzima minimalno 50% viewport širine.
Sadržaj fajla: max-width nema ograničenja, koristi sav prostor.
Visina: min-height 80vh da ne bude stisnuto.
## Fajlovi za izmenu
```
code/web/static/style.css ← širina docs containera
code/web/templates/docs.html ← ako treba layout fix
```
## Testovi
- Otvori /docs → zauzima puno ekrana
- Renderovan markdown čitljiv na celoj širini
- Responsive: na manjem ekranu 100% širine
---
## Pitanja
---
## Odgovori

View File

@ -30,6 +30,7 @@ type docsViewData struct {
Path string
Breadcrumbs []breadcrumb
HTML htmltpl.HTML
Files []docFile
}
// breadcrumb represents one segment of the navigation path.
@ -89,10 +90,24 @@ func (s *Server) handleDocsView(c *gin.Context) {
// Render markdown to HTML
rendered := renderMarkdown(content, relPath)
// HTMX request → return just the content fragment
if c.GetHeader("HX-Request") == "true" {
c.Header("Content-Type", "text/html; charset=utf-8")
breadcrumbHTML := `<div class="docs-breadcrumbs"><a href="/docs">Dokumenti</a>`
for _, bc := range buildBreadcrumbs(relPath) {
breadcrumbHTML += ` <span class="breadcrumb-sep"></span> <a href="/docs/` + bc.Path + `">` + bc.Name + `</a>`
}
breadcrumbHTML += `</div>`
c.String(http.StatusOK, breadcrumbHTML+rendered)
return
}
// Full page request → return with sidebar
data := docsViewData{
Path: relPath,
Breadcrumbs: buildBreadcrumbs(relPath),
HTML: htmltpl.HTML(rendered),
Files: scanMarkdownFiles(s.projectRoot()),
}
c.Header("Content-Type", "text/html; charset=utf-8")

View File

@ -928,6 +928,64 @@ func TestConsoleHistory_AfterExec(t *testing.T) {
}
}
func TestDocsView_HasSidebarLayout(t *testing.T) {
srv := setupTestServer(t)
req := httptest.NewRequest(http.MethodGet, "/docs/CLAUDE.md", nil)
w := httptest.NewRecorder()
srv.Router.ServeHTTP(w, req)
body := w.Body.String()
if !containsStr(body, "docs-layout") {
t.Error("expected docs-layout class for grid layout")
}
if !containsStr(body, "docs-sidebar") {
t.Error("expected docs-sidebar class")
}
if !containsStr(body, "docs-main") {
t.Error("expected docs-main class")
}
// Sidebar should list files
if !containsStr(body, "README.md") {
t.Error("expected file list in sidebar")
}
}
func TestDocsView_HTMXReturnsFragment(t *testing.T) {
srv := setupTestServer(t)
req := httptest.NewRequest(http.MethodGet, "/docs/CLAUDE.md", nil)
req.Header.Set("HX-Request", "true")
w := httptest.NewRecorder()
srv.Router.ServeHTTP(w, req)
body := w.Body.String()
// Should NOT have full page HTML
if containsStr(body, "<!DOCTYPE html>") {
t.Error("HTMX request should return fragment, not full page")
}
// Should have breadcrumbs and content
if !containsStr(body, "Dokumenti") {
t.Error("expected breadcrumbs in fragment")
}
if !containsStr(body, "Glavni fajl") {
t.Error("expected rendered content in fragment")
}
}
func TestDocsList_HasSidebarLayout(t *testing.T) {
srv := setupTestServer(t)
req := httptest.NewRequest(http.MethodGet, "/docs", nil)
w := httptest.NewRecorder()
srv.Router.ServeHTTP(w, req)
body := w.Body.String()
if !containsStr(body, "docs-layout") {
t.Error("expected docs-layout class on docs list page")
}
}
func TestRewriteLinksSimple(t *testing.T) {
input := `<a href="README.md">link</a> and <a href="https://example.com">ext</a>`
result := rewriteLinksSimple(input, ".")

View File

@ -349,13 +349,20 @@ body {
/* Docs */
.docs-container {
padding: 16px 24px;
max-width: 960px;
margin: 0 auto;
min-height: 80vh;
}
.docs-container h2 {
margin-bottom: 16px;
.docs-layout {
display: grid;
grid-template-columns: 25% 1fr;
gap: 16px;
min-height: 80vh;
}
.docs-sidebar h2 {
margin-bottom: 12px;
color: #e94560;
font-size: 1.1em;
}
.docs-list {
@ -403,6 +410,10 @@ body {
margin: 0 4px;
}
.docs-main {
min-width: 0;
}
.docs-content {
background: #16213e;
border-radius: 8px;
@ -609,6 +620,7 @@ body {
@media (max-width: 700px) {
.board { grid-template-columns: repeat(2, 1fr); }
#task-detail { width: 100%; right: -100%; }
.docs-layout { grid-template-columns: 1fr; }
}
@media (max-width: 500px) {

View File

@ -18,16 +18,24 @@
</nav>
</div>
<div class="docs-container">
<h2>Dokumentacija</h2>
<div class="docs-list">
{{range .Files}}
<a href="/docs/{{.Path}}" class="doc-item" hx-get="/docs/{{.Path}}" hx-target="#docs-content" hx-push-url="true">
<span class="doc-icon">📄</span>
<span class="doc-name">{{.Name}}</span>
</a>
{{end}}
<div class="docs-layout">
<div class="docs-sidebar">
<h2>Dokumentacija</h2>
<div class="docs-list">
{{range .Files}}
<a href="/docs/{{.Path}}" class="doc-item" hx-get="/docs/{{.Path}}" hx-target="#docs-content" hx-push-url="true">
<span class="doc-icon">📄</span>
<span class="doc-name">{{.Name}}</span>
</a>
{{end}}
</div>
</div>
<div class="docs-main">
<div class="docs-content" id="docs-content">
<p style="color:#888">Klikni na fajl da vidiš sadržaj.</p>
</div>
</div>
</div>
<div id="docs-content"></div>
</div>
</body>
</html>

View File

@ -18,15 +18,30 @@
</nav>
</div>
<div class="docs-container">
<div class="docs-breadcrumbs">
<a href="/docs">Dokumenti</a>
{{range .Breadcrumbs}}
<span class="breadcrumb-sep"></span>
<a href="/docs/{{.Path}}">{{.Name}}</a>
{{end}}
</div>
<div class="docs-content" id="docs-content">
{{.HTML}}
<div class="docs-layout">
<div class="docs-sidebar">
<h2>Dokumentacija</h2>
<div class="docs-list">
{{range .Files}}
<a href="/docs/{{.Path}}" class="doc-item" hx-get="/docs/{{.Path}}" hx-target="#docs-content" hx-push-url="true">
<span class="doc-icon">📄</span>
<span class="doc-name">{{.Name}}</span>
</a>
{{end}}
</div>
</div>
<div class="docs-main">
<div class="docs-content" id="docs-content">
<div class="docs-breadcrumbs">
<a href="/docs">Dokumenti</a>
{{range .Breadcrumbs}}
<span class="breadcrumb-sep"></span>
<a href="/docs/{{.Path}}">{{.Name}}</a>
{{end}}
</div>
{{.HTML}}
</div>
</div>
</div>
</div>
</body>