package editor import ( "time" "pad/internal/ui" ) // Frame is the unit of handoff from the logic goroutine to the frame // receiver. Per architecture.md §9, the frame is the ONLY cross-goroutine // state carrier: the main goroutine must never read *State directly, it // reads the snapshot stored here by the frame receiver (under the frame // mutex). // // Elems is the computed element tree for the next draw. // The remaining fields are the view-state snapshot that the main goroutine // needs to route events and forward input: // // - Scale: current px-per-Dp, so the main goroutine can detect scale // changes and pass the scale to the renderer's Draw call. // - FocusedElementID: which registered element receives key/edit events. // - Query: the search query the logic goroutine is currently filtering // with; the main goroutine compares it against the (main-owned) search // widget's text and forwards changes via SearchQueryChan. type Frame struct { Elems []ui.Element Scale float32 FontScale float32 // user font-size setting the logic bookkeeping used FocusedElementID string Query string } // frameOf wraps a computed element tree with the current view-state // snapshot. Must be called on the logic goroutine. func (l *Logic) frameOf(elems []ui.Element) Frame { return Frame{ Elems: elems, Scale: l.state.scale, FontScale: l.state.fontScale, FocusedElementID: l.state.FocusedElementID, Query: l.state.Browser.Query, } } // inspectReq is a test-only request to run fn on the logic goroutine. // It preserves the single-owner invariant (architecture.md §1): the fn // executes on the owner, not on the caller. fn must not block on sends to // logic channels. type inspectReq struct { fn func(st *State) any resp chan any } // Inspect runs fn on the logic goroutine and returns its result. It is // intended for tests; production code must use the regular channels. func (l *Logic) Inspect(fn func(st *State) any) (any, bool) { req := &inspectReq{fn: fn, resp: make(chan any, 1)} select { case l.inspectChan <- req: case <-time.After(5 * time.Second): return nil, false } select { case v := <-req.resp: return v, true case <-time.After(5 * time.Second): return nil, false } }