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
|
Path string
|
||||||
Breadcrumbs []breadcrumb
|
Breadcrumbs []breadcrumb
|
||||||
HTML htmltpl.HTML
|
HTML htmltpl.HTML
|
||||||
|
Files []docFile
|
||||||
}
|
}
|
||||||
|
|
||||||
// breadcrumb represents one segment of the navigation path.
|
// breadcrumb represents one segment of the navigation path.
|
||||||
@ -89,10 +90,24 @@ func (s *Server) handleDocsView(c *gin.Context) {
|
|||||||
// Render markdown to HTML
|
// Render markdown to HTML
|
||||||
rendered := renderMarkdown(content, relPath)
|
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{
|
data := docsViewData{
|
||||||
Path: relPath,
|
Path: relPath,
|
||||||
Breadcrumbs: buildBreadcrumbs(relPath),
|
Breadcrumbs: buildBreadcrumbs(relPath),
|
||||||
HTML: htmltpl.HTML(rendered),
|
HTML: htmltpl.HTML(rendered),
|
||||||
|
Files: scanMarkdownFiles(s.projectRoot()),
|
||||||
}
|
}
|
||||||
|
|
||||||
c.Header("Content-Type", "text/html; charset=utf-8")
|
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) {
|
func TestRewriteLinksSimple(t *testing.T) {
|
||||||
input := `<a href="README.md">link</a> and <a href="https://example.com">ext</a>`
|
input := `<a href="README.md">link</a> and <a href="https://example.com">ext</a>`
|
||||||
result := rewriteLinksSimple(input, ".")
|
result := rewriteLinksSimple(input, ".")
|
||||||
|
|||||||
@ -349,13 +349,20 @@ body {
|
|||||||
/* Docs */
|
/* Docs */
|
||||||
.docs-container {
|
.docs-container {
|
||||||
padding: 16px 24px;
|
padding: 16px 24px;
|
||||||
max-width: 960px;
|
min-height: 80vh;
|
||||||
margin: 0 auto;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.docs-container h2 {
|
.docs-layout {
|
||||||
margin-bottom: 16px;
|
display: grid;
|
||||||
|
grid-template-columns: 25% 1fr;
|
||||||
|
gap: 16px;
|
||||||
|
min-height: 80vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
.docs-sidebar h2 {
|
||||||
|
margin-bottom: 12px;
|
||||||
color: #e94560;
|
color: #e94560;
|
||||||
|
font-size: 1.1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.docs-list {
|
.docs-list {
|
||||||
@ -403,6 +410,10 @@ body {
|
|||||||
margin: 0 4px;
|
margin: 0 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.docs-main {
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.docs-content {
|
.docs-content {
|
||||||
background: #16213e;
|
background: #16213e;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
@ -609,6 +620,7 @@ body {
|
|||||||
@media (max-width: 700px) {
|
@media (max-width: 700px) {
|
||||||
.board { grid-template-columns: repeat(2, 1fr); }
|
.board { grid-template-columns: repeat(2, 1fr); }
|
||||||
#task-detail { width: 100%; right: -100%; }
|
#task-detail { width: 100%; right: -100%; }
|
||||||
|
.docs-layout { grid-template-columns: 1fr; }
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 500px) {
|
@media (max-width: 500px) {
|
||||||
|
|||||||
@ -18,16 +18,24 @@
|
|||||||
</nav>
|
</nav>
|
||||||
</div>
|
</div>
|
||||||
<div class="docs-container">
|
<div class="docs-container">
|
||||||
<h2>Dokumentacija</h2>
|
<div class="docs-layout">
|
||||||
<div class="docs-list">
|
<div class="docs-sidebar">
|
||||||
{{range .Files}}
|
<h2>Dokumentacija</h2>
|
||||||
<a href="/docs/{{.Path}}" class="doc-item" hx-get="/docs/{{.Path}}" hx-target="#docs-content" hx-push-url="true">
|
<div class="docs-list">
|
||||||
<span class="doc-icon">📄</span>
|
{{range .Files}}
|
||||||
<span class="doc-name">{{.Name}}</span>
|
<a href="/docs/{{.Path}}" class="doc-item" hx-get="/docs/{{.Path}}" hx-target="#docs-content" hx-push-url="true">
|
||||||
</a>
|
<span class="doc-icon">📄</span>
|
||||||
{{end}}
|
<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>
|
||||||
<div id="docs-content"></div>
|
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@ -18,15 +18,30 @@
|
|||||||
</nav>
|
</nav>
|
||||||
</div>
|
</div>
|
||||||
<div class="docs-container">
|
<div class="docs-container">
|
||||||
<div class="docs-breadcrumbs">
|
<div class="docs-layout">
|
||||||
<a href="/docs">Dokumenti</a>
|
<div class="docs-sidebar">
|
||||||
{{range .Breadcrumbs}}
|
<h2>Dokumentacija</h2>
|
||||||
<span class="breadcrumb-sep">›</span>
|
<div class="docs-list">
|
||||||
<a href="/docs/{{.Path}}">{{.Name}}</a>
|
{{range .Files}}
|
||||||
{{end}}
|
<a href="/docs/{{.Path}}" class="doc-item" hx-get="/docs/{{.Path}}" hx-target="#docs-content" hx-push-url="true">
|
||||||
</div>
|
<span class="doc-icon">📄</span>
|
||||||
<div class="docs-content" id="docs-content">
|
<span class="doc-name">{{.Name}}</span>
|
||||||
{{.HTML}}
|
</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>
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user