clone

a lazygit-style terminal viewer for openspec repos: watch changes, tasks and spec deltas land as agents work

Add license, Makefile, CI and release workflows, GoReleaser config; scroll the tab bar to keep the active tab visible

Dami committed 92b186a
commit
92b186aef648f523a272e229177ee9d221ad7f63
parent
6aed9ac7d1833d3682b605c8a179716ab5f74dac
author
Dami <dami.etoile@gmail.com>
12 files changed, +278 -33
A.github/workflows/ci.yml55++++++++++++++++++++++++++++++
A.github/workflows/release.yml37++++++++++++++++++++
M.gitignore2+
A.goreleaser.yaml35+++++++++++++++++++
ALICENSE21+++++++++++
AMakefile28+++++++++++++++
Minternal/spec/load_test.go19----------
Ainternal/spec/load_unix_test.go28+++++++++++++++
Minternal/ui/mainpane.go39++++++++++++++++++++--
Minternal/ui/model.go9-----
Minternal/ui/model_test.go20+++++++++++
Mmain.go18+++++++++-
A .github/workflows/ci.yml +55 -0
A .github/workflows/release.yml +37 -0
M .gitignore +2 -0
A .goreleaser.yaml +35 -0
A LICENSE +21 -0
A Makefile +28 -0
M internal/spec/load_test.go +0 -19
A internal/spec/load_unix_test.go +28 -0
M internal/ui/mainpane.go +36 -3
@@ -8,6 +8,7 @@ import (
88 99 tea "charm.land/bubbletea/v2"1010 "charm.land/glamour/v2"11+ "charm.land/lipgloss/v2"1112 1213 "github.com/FelineStateMachine/lazyopenspec/internal/gitx"1314 "github.com/FelineStateMachine/lazyopenspec/internal/spec"
@@ -352,12 +353,14 @@ func (m Model) tabBar(t target, w int) string {
352353 tabs := m.tabsFor(t)353354 cur, _ := m.currentTab(t)354355 var parts []string355- for _, tb := range tabs {356+ active := 0357+ for i, tb := range tabs {356358 // Style each piece separately: underlined styles re-wrap every rune357359 // and would mangle an already-styled substring.358360 st := th.tabInactive359361 if tb.key == cur.key {360362 st = th.tabActive363+ active = i361364 }362365 part := st.Render(tb.label)363366 if m.now.Sub(tb.mod) < freshWindow {
@@ -368,12 +371,42 @@ func (m Model) tabBar(t target, w int) string {
368371 }369372 parts = append(parts, part)370373 }371- left := " " + strings.Join(parts, th.subtle.Render(" │ "))372374 right := ""373375 if m.diffMode {374376 right = th.modSt.Render("[diff vs HEAD]") + " "375377 }376- return spread(left, right, w)378+ return spread(m.tabWindow(parts, active, w-lipgloss.Width(right)-1), right, w)379+}380+381+// tabWindow joins as many tabs as fit in w cells, always including the active382+// one, and marks tabs hidden off either edge with an arrow.383+func (m Model) tabWindow(parts []string, active, w int) string {384+ sep := m.theme.subtle.Render(" │ ")385+ join := func(lo, hi int) string {386+ s := " "387+ if lo > 0 {388+ s += m.theme.muted.Render("‹ ")389+ }390+ s += strings.Join(parts[lo:hi], sep)391+ if hi < len(parts) {392+ s += m.theme.muted.Render(" ›")393+ }394+ return s395+ }396+ if len(parts) == 0 {397+ return " "398+ }399+ lo, hi := active, active+1400+ for grew := true; grew; {401+ grew = false402+ if hi < len(parts) && lipgloss.Width(join(lo, hi+1)) <= w {403+ hi, grew = hi+1, true404+ }405+ if lo > 0 && lipgloss.Width(join(lo-1, hi)) <= w {406+ lo, grew = lo-1, true407+ }408+ }409+ return join(lo, hi)377410 }378411 379412 func (m Model) gitMark(path string) string {
M internal/ui/model.go +0 -9
M internal/ui/model_test.go +20 -0
M main.go +16 -2