uiserver

package
v0.12.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 8, 2026 License: MIT Imports: 32 Imported by: 0

Documentation

Overview

Package uiserver: livepublish.go is the server-side engine of live push (Phase 6, LIV-01/LIV-03): a store watcher that learns a re-index happened, a change detector that decides whether that is worth telling anyone, and a bounded, coalescing fan-out registry that hands the news to every open stream without letting a slow reader hurt a fast one.

This file has NO HTTP or Connect dependency of any kind — the streaming handler consumes livePublisher, not the other way round. Every type and function here is unexported: wiring this into the wire layer is the handler's job, not this file's.

Package uiserver implements codegraph ui's local, loopback-only Connect RPC server. originguard.go is the first piece to exist in this package (SRV-02): the exact-match Origin/Host control that must run before any RPC handler is reachable at all — the ROADMAP's blocking order names this file, not a handler, as this phase's Task 1.

permalink.go implements GetPermalink (D-06/D-07/D-08/D-09): it turns a repo-relative path and an optional line/end_line anchor into a GitHub blob URL pinned to the commit the INDEX was built at, never HEAD, so the remote view matches what the UI just showed.

Everything this handler produces from git introspection is an ANSWER, never an error: no remote, a non-GitHub forge, an unpushed commit, or an absent commit_sha all render as a SUCCESSFUL response carrying PERMALINK_AVAILABILITY_NO_LINK or PERMALINK_AVAILABILITY_LINKABLE_UNVERIFIED with a populated reason — the same "empty is a successful response" discipline Explore already follows (D-07). The one case that IS an error is an invalid path argument, handled by (*query.Engine). ValidateRepoRelativePath (SRV-05) below.

spa.go implements codegraph ui's SPA app-shell handler (D-12): it serves the embedded, committed SvelteKit build (web.BuildFS, BLD-02) at "/" on the same mux the Connect RPC handler is already mounted on (D-09 — Go 1.26 http.ServeMux's most-specific-pattern-wins resolves "/codegraph.ui.v1.UIService/" ahead of "/" with no allowlist to keep in sync). D-10's asset-vs-route discrimination is implemented as a three-way rule: a miss under the immutable-asset prefix is a 404 — deliberately, by design, never a fallback, because serving HTML for a missing hashed chunk produces a browser MIME/module error that points nowhere near the real cause; a hit anywhere else that resolves to a real file is served as itself; anything else falls back to index.html so SvelteKit's client router can take over. D-11's two-class Cache-Control policy and a same-origin Content-Security-Policy (with script-src hashes derived from the embedded index.html itself) are set unconditionally on every response.

Package-level doc comment lives in originguard.go — not repeated here.

Package uiserver: watchtimeout.go implements D-02 — clearing http.Server's absolute WriteTimeout for exactly one path, the generated WatchGraph streaming procedure, and no other.

writeTimeout (server.go) is an ABSOLUTE deadline on an entire response write, sized for a unary rpc's slowest legitimate response (server.go's own comment: "roughly two orders of magnitude above" that). A server-streaming rpc's response is intentionally long-lived — it has no "response finished" moment short of the client disconnecting — so the same 60-second bound that protects every unary rpc would kill a healthy stream. Clearing it here, for this path only, is the deliberate relaxation D-02 chose over its two rejected alternatives: WriteTimeout: 0 server-wide (trades away a documented safety property across all 13 unary rpcs and the SPA handler to serve one method) and forced client reconnects under 60s (churns every open tab at least once a minute, close to the reconnect storm LIV-03 exists to prevent).

connectrpc.com/connect v1.20.0's checkServerStreamsCanFlush (protocol.go:347-350) gates entry to EVERY server-streaming handler on a bare type assertion against http.Flusher, with no Unwrap() traversal:

if _, flushable := responseWriter.(http.Flusher); requiresFlusher && !flushable {
	return NewError(CodeInternal, fmt.Errorf("%T does not implement http.Flusher", responseWriter))
}

A middleware that wraps w in any custom type — even one that itself forwards Flush by embedding — hides the assertion's target from that check and the handler is refused outright, not merely degraded. clearWatchDeadline therefore calls http.NewResponseController(w) for its side effect only and passes the ORIGINAL w straight through to next, the exact shape originHostGuard (originguard.go) already establishes as this package's one other middleware.

Index

Constants

View Source
const DefaultAddr = "127.0.0.1:0"

DefaultAddr is the ephemeral loopback bind address D-07 mandates: with a ":0" port, no concrete port exists until net.Listen actually returns, so Listen always resolves an empty Options.Addr to this value rather than a fixed, potentially-colliding port. A second `codegraph ui` simply gets a different port.

Variables

This section is empty.

Functions

This section is empty.

Types

type Options

type Options struct {
	// RepoPath is the single repository uiService answers every rpc
	// for.
	RepoPath string

	// Addr is the bind address net.Listen receives, defaulting to
	// DefaultAddr when empty. Nothing outside this package writes this
	// field in v1 — no flag, no environment variable, no configuration
	// file reads it (D-08). A later `--host`/`--port` is therefore an
	// override of a field that already exists, not a restructuring
	// (SRV-03).
	Addr string
}

Options configures Listen. RepoPath and Addr are the ONLY two fields — asserted by TestUIServiceHoldsNoStoreTypedField's sibling assertion in server_test.go, a reflected field-set equality with a length-2 check — so neither an unwired option nor a second browser-launch owner can be reintroduced. Browser-launch policy and the launch itself live entirely in internal/cli, never here: one owner means neither an unused option nor a double launch (review MEDIUM 01-06).

type Server

type Server struct {
	// contains filtered or unexported fields
}

Server is a bound codegraph ui listener: bound the instant Listen returns, serving only once Serve is called. The split exists because the port cannot be published before the bind, and the bind cannot live inside the blocking call — see Listen's own doc comment.

func Listen

func Listen(o Options) (*Server, error)

Listen binds addr (defaulting to DefaultAddr), extracts the concrete bound port, builds the http.ServeMux with the UIService Connect handler mounted on it, wraps the WHOLE mux in originHostGuard — the guard is the outermost http.Handler, never a connect interceptor, because an interceptor runs after routing and message parsing have already begun and would not protect Phase 2's static SPA routes either — and returns. The bind happens here, inside Listen, and never inside Serve: with an ephemeral ":0" bind the port does not exist until net.Listen returns, and (*Server).URL must already be real the instant Listen returns, which is only possible because Listen itself never blocks.

func (*Server) Close

func (s *Server) Close() error

Close closes the listener without ever calling Serve — for a caller that binds and then decides not to serve. Stops the publisher first (see stopPublisher's own doc comment), so Listen followed directly by Close leaks no fsnotify watcher or debouncer goroutine.

func (*Server) Serve

func (s *Server) Serve(ctx context.Context) error

Serve blocks until ctx is cancelled or the underlying http.Server stops on its own, normalizing http.ErrServerClosed to nil at every return path — codegraph ui returns this value straight from RunE, so a normalized nil is the difference between a clean Ctrl-C and a spurious non-zero exit code.

http.Server.Serve blocks, so a literal "serve, then on ctx.Done() shut down" would never reach the cancellation branch until serving had already stopped by itself. Serve therefore runs the blocking Serve call in a goroutine over a BUFFERED capacity-1 error channel and selects on that channel against ctx.Done(): buffered, so the goroutine can always deposit its result and exit even if nobody is left reading — an unbuffered channel here would leak the goroutine on the cancellation path.

func (*Server) URL

func (s *Server) URL() string

URL returns the server's connectable base URL, valid the instant Listen returns — before Serve is ever called.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL