From 6720359360021735261047104ac281b8129e3ea2 Mon Sep 17 00:00:00 2001 From: Greg Pomerantz Date: Thu, 7 May 2026 17:45:56 -0400 Subject: [PATCH] Add Gioui scaffolding with Label and ListView rendering - Element interface with Region() and Visible() methods - Label and ListView element types with constructors - Renderer that clips and draws elements in slice order - Main app loop with Gioui window and frame events Co-authored-by: Qwen-Coder --- cmd/pad/main.go | 57 ++++++ doc/conflict_resolution.md | 233 +++++++++++++++++++++++ doc/element_model.md | 378 +++++++++++++++++++++++++++++++++++++ go.mod | 14 ++ go.sum | 21 +++ internal/ui/element.go | 106 +++++++++++ internal/ui/render.go | 70 +++++++ 7 files changed, 879 insertions(+) create mode 100644 cmd/pad/main.go create mode 100644 doc/conflict_resolution.md create mode 100644 doc/element_model.md create mode 100644 go.mod create mode 100644 go.sum create mode 100644 internal/ui/element.go create mode 100644 internal/ui/render.go diff --git a/cmd/pad/main.go b/cmd/pad/main.go new file mode 100644 index 0000000..e54abdf --- /dev/null +++ b/cmd/pad/main.go @@ -0,0 +1,57 @@ +package main + +import ( + "log" + + "gioui.org/app" + "gioui.org/op" + "gioui.org/text" + "gioui.org/unit" + + "pad/internal/ui" +) + +func main() { + go func() { + w := new(app.Window) + w.Option(app.Title("Pad")) + w.Option(app.Size(unit.Dp(390), unit.Dp(844))) + if err := run(w); err != nil { + log.Fatal(err) + } + }() + app.Main() +} + +func run(w *app.Window) error { + var ops op.Ops + + shaper := text.NewShaper() + renderer := ui.New(ui.Theme{FontSize: 14}, shaper) + + elems := starterElements() + + for { + switch e := w.Event().(type) { + case app.DestroyEvent: + return e.Err + case app.FrameEvent: + gtx := app.NewContext(&ops, e) + renderer.Draw(gtx, elems) + e.Frame(&ops) + } + } +} + +// starterElements returns a hardcoded set of elements for initial testing. +func starterElements() []ui.Element { + return []ui.Element{ + ui.NewLabel("Pad", 20, ui.Region{X: 0, Y: 0, W: 390, H: 48}), + ui.NewListView(ui.Region{X: 0, Y: 48, W: 390, H: 700}, + []ui.ListItem{ + {Text: "notes.txt"}, + {Text: "ideas.txt"}, + {Text: "todo.txt"}, + }), + } +} diff --git a/doc/conflict_resolution.md b/doc/conflict_resolution.md new file mode 100644 index 0000000..0c91c02 --- /dev/null +++ b/doc/conflict_resolution.md @@ -0,0 +1,233 @@ +# Conflict Resolution Specification + +## 1. Overview + +Pad operates on a directory synced by Syncthing. External modifications to files originate from edits on other devices, delivered via Syncthing. This document specifies how Pad detects, classifies, and resolves such changes. + +## 2. Syncthing Conflict File Format + +When Syncthing detects that two devices have modified the same file, it preserves both versions: + +- **Original file** (`notes.txt`) — the local device's version (what Pad edited) +- **Conflict file** (`notes.sync-conflict-2024-05-15-1430-ABCDEF1.txt`) — the remote device's version + +**Naming pattern:** `.sync-conflict--