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:
parent
70e2ee684f
commit
0e6d0ecd66
50
TASKS/reports/T15-report.md
Normal file
50
TASKS/reports/T15-report.md
Normal 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
53
TASKS/review/T15.md
Normal 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
|
||||
@ -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")
|
||||
|
||||
@ -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, ".")
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -18,6 +18,8 @@
|
||||
</nav>
|
||||
</div>
|
||||
<div class="docs-container">
|
||||
<div class="docs-layout">
|
||||
<div class="docs-sidebar">
|
||||
<h2>Dokumentacija</h2>
|
||||
<div class="docs-list">
|
||||
{{range .Files}}
|
||||
@ -27,7 +29,13 @@
|
||||
</a>
|
||||
{{end}}
|
||||
</div>
|
||||
<div id="docs-content"></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>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@ -18,6 +18,20 @@
|
||||
</nav>
|
||||
</div>
|
||||
<div class="docs-container">
|
||||
<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}}
|
||||
@ -25,9 +39,10 @@
|
||||
<a href="/docs/{{.Path}}">{{.Name}}</a>
|
||||
{{end}}
|
||||
</div>
|
||||
<div class="docs-content" id="docs-content">
|
||||
{{.HTML}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user