diff --git a/src/app/controller/preview-session.go b/src/app/controller/preview-session.go new file mode 100644 index 0000000..9e4448a --- /dev/null +++ b/src/app/controller/preview-session.go @@ -0,0 +1,49 @@ +package controller + +import ( + "sync/atomic" + "time" + + "github.com/snivilised/jaywalk/src/agenor/core" +) + +// PreviewSession is a lightweight session implementation for the tweak +// preview traversal. It implements core.Session but strips out all +// persistence, resume, and fault handling. Timing methods return zero +// values — they are not meaningful for a disposable preview session. +// Ctrl-C exits cleanly via context cancellation in the caller — no +// navigation state is saved. +type PreviewSession struct { + done atomic.Bool +} + +// NewPreviewSession creates a PreviewSession with IsComplete() == false. +func NewPreviewSession() *PreviewSession { + return &PreviewSession{} +} + +// IsComplete returns true when MarkComplete has been called. +func (s *PreviewSession) IsComplete() bool { + return s.done.Load() +} + +// StartedAt returns the zero time. Not meaningful for preview sessions. +func (s *PreviewSession) StartedAt() time.Time { + return time.Time{} +} + +// Elapsed returns 0. Not meaningful for preview sessions. +func (s *PreviewSession) Elapsed() time.Duration { + return 0 +} + +// MarkComplete sets the session as completed. Called by the tweak +// coordinator when a preview traversal finishes normally. On Ctrl-C +// the context cancels the traversal and MarkComplete is never called, +// so IsComplete remains false. +func (s *PreviewSession) MarkComplete() { + s.done.Store(true) +} + +// compile-time check: *PreviewSession satisfies core.Session. +var _ core.Session = (*PreviewSession)(nil) diff --git a/src/app/controller/preview-session_test.go b/src/app/controller/preview-session_test.go new file mode 100644 index 0000000..c7c2eea --- /dev/null +++ b/src/app/controller/preview-session_test.go @@ -0,0 +1,63 @@ +package controller_test + +import ( + "time" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + "github.com/snivilised/jaywalk/src/agenor/core" + jac "github.com/snivilised/jaywalk/src/app/controller" +) + +var _ = Describe("PreviewSession", func() { + var session *jac.PreviewSession + + BeforeEach(func() { + session = jac.NewPreviewSession() + }) + + Describe("NewPreviewSession", func() { + It("starts with IsComplete == false", func() { + Expect(session.IsComplete()).To(BeFalse()) + }) + }) + + Describe("MarkComplete", func() { + It("sets IsComplete to true", func() { + session.MarkComplete() + Expect(session.IsComplete()).To(BeTrue()) + }) + + It("can be called multiple times (idempotent)", func() { + session.MarkComplete() + session.MarkComplete() + Expect(session.IsComplete()).To(BeTrue()) + }) + }) + + Describe("StartedAt", func() { + It("returns the zero time", func() { + Expect(session.StartedAt()).To(Equal(time.Time{})) + }) + }) + + Describe("Elapsed", func() { + It("returns 0", func() { + Expect(session.Elapsed()).To(Equal(time.Duration(0))) + }) + }) + + Describe("core.Session implementation", func() { + It("satisfies the core.Session interface", func() { + var s core.Session = session + Expect(s).NotTo(BeNil()) + }) + + It("can be assigned to a core.Session variable", func() { + var s core.Session = session + Expect(s.IsComplete()).To(BeFalse()) + session.MarkComplete() + Expect(s.IsComplete()).To(BeTrue()) + }) + }) +}) diff --git a/src/prism/contract/messages.go b/src/prism/contract/messages.go new file mode 100644 index 0000000..23a775c --- /dev/null +++ b/src/prism/contract/messages.go @@ -0,0 +1,9 @@ +package contract + +// ThemeUpdateMsg carries a freshly built Theme to the active view +// model. Sent by tweak whenever a working-state palette change occurs. +// Each view model's Update method gains a ThemeUpdateMsg case that +// stores the new theme. The next render uses it automatically. +type ThemeUpdateMsg struct { + Theme Theme +}