From 93597a2c45ce973d0a5ae2760f9790ff8e3fb557 Mon Sep 17 00:00:00 2001 From: mirivlad Date: Sun, 28 Jun 2026 04:00:50 +0800 Subject: [PATCH] Tag browser captures with current workspace --- internal/core/browserreceiver/receiver.go | 32 ++++++++++++-- .../core/browserreceiver/receiver_test.go | 42 +++++++++++++++++++ main.go | 8 +++- 3 files changed, 77 insertions(+), 5 deletions(-) diff --git a/internal/core/browserreceiver/receiver.go b/internal/core/browserreceiver/receiver.go index 38443ba..f2c7e64 100644 --- a/internal/core/browserreceiver/receiver.go +++ b/internal/core/browserreceiver/receiver.go @@ -19,9 +19,12 @@ const capturePath = "/api/browser-inbox/v1/captures" const DefaultAddr = "127.0.0.1:47731" type Receiver struct { - bus *events.Bus + bus *events.Bus + workspaceProvider WorkspaceProvider } +type WorkspaceProvider func() string + type Server struct { listener net.Listener server *http.Server @@ -59,8 +62,12 @@ type CaptureBrowser struct { Name string `json:"name"` } -func New(bus *events.Bus) *Receiver { - return &Receiver{bus: bus} +func New(bus *events.Bus, providers ...WorkspaceProvider) *Receiver { + var provider WorkspaceProvider + if len(providers) > 0 { + provider = providers[0] + } + return &Receiver{bus: bus, workspaceProvider: provider} } func Start(addr string, receiver *Receiver) (*Server, error) { @@ -128,10 +135,12 @@ func (r *Receiver) ServeHTTP(w http.ResponseWriter, req *http.Request) { writeError(w, http.StatusServiceUnavailable, "browser inbox unavailable") return } + eventPayload := payload.EventPayload() + r.annotateWorkspace(eventPayload) r.bus.Publish(events.Event{ Name: eventName, Timestamp: time.Now().UTC().Format(time.RFC3339Nano), - Payload: payload.EventPayload(), + Payload: eventPayload, }) w.WriteHeader(http.StatusAccepted) @@ -141,6 +150,21 @@ func (r *Receiver) ServeHTTP(w http.ResponseWriter, req *http.Request) { }) } +func (r *Receiver) annotateWorkspace(payload map[string]interface{}) { + if r == nil || r.workspaceProvider == nil || payload == nil { + return + } + if _, ok := payload["workspaceRootPath"]; ok { + return + } + workspaceRoot := strings.TrimSpace(r.workspaceProvider()) + if workspaceRoot == "" { + return + } + payload["workspaceRootPath"] = workspaceRoot + payload["workspaceName"] = workspaceRoot +} + func (p CapturePayload) Validate() error { if p.SchemaVersion != 1 { return fmt.Errorf("unsupported schemaVersion") diff --git a/internal/core/browserreceiver/receiver_test.go b/internal/core/browserreceiver/receiver_test.go index dae7383..0520ff5 100644 --- a/internal/core/browserreceiver/receiver_test.go +++ b/internal/core/browserreceiver/receiver_test.go @@ -85,6 +85,48 @@ func TestReceiverAcceptsSelectionCaptureAndPublishesEvent(t *testing.T) { } } +func TestReceiverAnnotatesCaptureWithCurrentWorkspace(t *testing.T) { + bus := events.NewBus() + received := make(chan events.Event, 1) + bus.Subscribe("browser.capture.page", func(event events.Event) { + received <- event + }) + + receiver := New(bus, func() string { return "Project" }) + body := `{ + "schemaVersion": 1, + "captureId": "capture-workspace", + "capturedAt": "2026-06-27T00:00:00.000Z", + "source": "verstak-browser-extension", + "kind": "page", + "page": { + "url": "https://example.com/article", + "title": "Example Article" + } + }` + + req := httptest.NewRequest(http.MethodPost, "/api/browser-inbox/v1/captures", bytes.NewBufferString(body)) + rec := httptest.NewRecorder() + + receiver.ServeHTTP(rec, req) + + if rec.Code != http.StatusAccepted { + t.Fatalf("status = %d, want %d; body=%s", rec.Code, http.StatusAccepted, rec.Body.String()) + } + + event := <-received + payload, ok := event.Payload.(map[string]interface{}) + if !ok { + t.Fatalf("event payload type = %T, want map[string]interface{}", event.Payload) + } + if payload["workspaceRootPath"] != "Project" { + t.Fatalf("payload workspaceRootPath = %v, want Project", payload["workspaceRootPath"]) + } + if payload["workspaceName"] != "Project" { + t.Fatalf("payload workspaceName = %v, want Project", payload["workspaceName"]) + } +} + func TestServerStartsOnLocalAddressAndAcceptsCapture(t *testing.T) { bus := events.NewBus() bus.Subscribe("browser.capture.page", func(event events.Event) {}) diff --git a/main.go b/main.go index 4f6e05a..dfb3e19 100644 --- a/main.go +++ b/main.go @@ -252,7 +252,13 @@ func main() { syncService = syncsvc.NewService(vaultService.GetVaultPath(), "") } app := api.NewApp(capRegistry, contribRegistry, permRegistry, eventBus, plugins, vaultService, storageService, filesService, appSettingsMgr, pluginStateMgr, workspaceMgr, syncService, debugEnabled) - browserReceiver := browserreceiver.New(eventBus) + browserReceiver := browserreceiver.New(eventBus, func() string { + current := app.GetCurrentWorkspace() + if root, ok := current["rootPath"].(string); ok { + return root + } + return "" + }) browserReceiverServer, err := browserreceiver.Start(browserreceiver.DefaultAddr, browserReceiver) if err != nil { log.Printf("[browserreceiver] local receiver disabled: %v", err)