Add core store route and AX cleanup
This commit is contained in:
parent
cd6a4c8d16
commit
07399bf260
3 changed files with 70 additions and 14 deletions
|
|
@ -393,16 +393,21 @@ type WSMessage struct {
|
|||
Data map[string]any `json:"data,omitempty"`
|
||||
}
|
||||
|
||||
// wsRequire extracts a string field from WS data and returns an error if it is empty.
|
||||
func wsRequire(data map[string]any, key string) (string, error) {
|
||||
// requireStringField extracts a string field from WebSocket data and fails when it is missing.
|
||||
func requireStringField(data map[string]any, key string) (string, error) {
|
||||
v, _ := data[key].(string)
|
||||
if v == "" {
|
||||
return "", coreerr.E("display.wsRequire", "missing required field \""+key+"\"", nil)
|
||||
return "", coreerr.E("display.requireStringField", "missing required field \""+key+"\"", nil)
|
||||
}
|
||||
return v, nil
|
||||
}
|
||||
|
||||
func wsOptions(data map[string]any) core.Options {
|
||||
// wsRequire is kept for backward compatibility inside the display package.
|
||||
func wsRequire(data map[string]any, key string) (string, error) {
|
||||
return requireStringField(data, key)
|
||||
}
|
||||
|
||||
func optionsFromMap(data map[string]any) core.Options {
|
||||
items := make([]core.Option, 0, len(data))
|
||||
for key, value := range data {
|
||||
items = append(items, core.Option{Key: key, Value: value})
|
||||
|
|
@ -410,6 +415,11 @@ func wsOptions(data map[string]any) core.Options {
|
|||
return core.NewOptions(items...)
|
||||
}
|
||||
|
||||
// wsOptions is kept for backward compatibility inside the display package.
|
||||
func wsOptions(data map[string]any) core.Options {
|
||||
return optionsFromMap(data)
|
||||
}
|
||||
|
||||
// handleWSMessage bridges WebSocket commands to IPC calls.
|
||||
func (s *Service) handleWSMessage(msg WSMessage) core.Result {
|
||||
ctx := context.Background()
|
||||
|
|
@ -998,13 +1008,13 @@ func (s *Service) SetWindowFullscreen(name string, fullscreen bool) error {
|
|||
|
||||
// SetWindowBackgroundColour sets the background colour of a window.
|
||||
func (s *Service) SetWindowBackgroundColour(name string, r, g, b, a uint8) error {
|
||||
res := s.Core().Action("window.setBackgroundColour").Run(context.Background(), core.NewOptions(
|
||||
result := s.Core().Action("window.setBackgroundColour").Run(context.Background(), core.NewOptions(
|
||||
core.Option{Key: "task", Value: window.TaskSetBackgroundColour{
|
||||
Name: name, Red: r, Green: g, Blue: b, Alpha: a,
|
||||
}},
|
||||
))
|
||||
if !res.OK {
|
||||
if e, ok := res.Value.(error); ok {
|
||||
if !result.OK {
|
||||
if e, ok := result.Value.(error); ok {
|
||||
return e
|
||||
}
|
||||
}
|
||||
|
|
@ -1221,10 +1231,10 @@ func (s *Service) GetEventManager() *WSEventManager {
|
|||
|
||||
func (s *Service) buildMenu() {
|
||||
items := []menu.MenuItem{
|
||||
{Role: ptr(menu.RoleAppMenu)},
|
||||
{Role: ptr(menu.RoleFileMenu)},
|
||||
{Role: ptr(menu.RoleViewMenu)},
|
||||
{Role: ptr(menu.RoleEditMenu)},
|
||||
{Role: pointerTo(menu.RoleAppMenu)},
|
||||
{Role: pointerTo(menu.RoleFileMenu)},
|
||||
{Role: pointerTo(menu.RoleViewMenu)},
|
||||
{Role: pointerTo(menu.RoleEditMenu)},
|
||||
{Label: "Workspace", Children: []menu.MenuItem{
|
||||
{Label: "New...", OnClick: s.handleNewWorkspace},
|
||||
{Label: "List", OnClick: s.handleListWorkspaces},
|
||||
|
|
@ -1240,8 +1250,8 @@ func (s *Service) buildMenu() {
|
|||
{Label: "Run", Accelerator: "CmdOrCtrl+R", OnClick: s.handleRun},
|
||||
{Label: "Build", Accelerator: "CmdOrCtrl+B", OnClick: s.handleBuild},
|
||||
}},
|
||||
{Role: ptr(menu.RoleWindowMenu)},
|
||||
{Role: ptr(menu.RoleHelpMenu)},
|
||||
{Role: pointerTo(menu.RoleWindowMenu)},
|
||||
{Role: pointerTo(menu.RoleHelpMenu)},
|
||||
}
|
||||
|
||||
// On non-macOS, remove the AppMenu role
|
||||
|
|
@ -1254,7 +1264,8 @@ func (s *Service) buildMenu() {
|
|||
))
|
||||
}
|
||||
|
||||
func ptr[T any](v T) *T { return &v }
|
||||
// pointerTo returns a pointer to value.
|
||||
func pointerTo[T any](value T) *T { return &value }
|
||||
|
||||
// --- Menu handler methods ---
|
||||
|
||||
|
|
|
|||
|
|
@ -111,6 +111,23 @@ func TestConfigTask_Good(t *testing.T) {
|
|||
assert.Equal(t, 800, cfg["default_width"])
|
||||
}
|
||||
|
||||
func TestResolveScheme_StoreRoute_Good(t *testing.T) {
|
||||
svc, _ := newTestDisplayService(t)
|
||||
|
||||
result := svc.ResolveScheme(context.Background(), "core://store?q=alpha")
|
||||
require.True(t, result.OK)
|
||||
|
||||
payload, ok := result.Value.(map[string]any)
|
||||
require.True(t, ok)
|
||||
assert.Equal(t, "text/html", payload["content_type"])
|
||||
|
||||
body, ok := payload["body"].(string)
|
||||
require.True(t, ok)
|
||||
assert.Contains(t, body, "core://store")
|
||||
assert.Contains(t, body, "storage scopes")
|
||||
assert.Contains(t, body, "Search the in-memory storage scopes")
|
||||
}
|
||||
|
||||
// --- Conclave integration tests ---
|
||||
|
||||
func TestServiceConclave_Good(t *testing.T) {
|
||||
|
|
|
|||
|
|
@ -40,6 +40,17 @@ func (s *Service) registerDefaultSchemes() {
|
|||
))
|
||||
}
|
||||
return s.Core().Action("gui.chat.conversations.list").Run(ctx, core.NewOptions())
|
||||
case "store":
|
||||
return core.Result{
|
||||
Value: map[string]any{
|
||||
"content_type": "text/html",
|
||||
"body": s.renderStoreSearchPage(query.Get("q")),
|
||||
"route": route,
|
||||
"url": "core://store",
|
||||
"query": query,
|
||||
},
|
||||
OK: true,
|
||||
}
|
||||
default:
|
||||
return core.Result{
|
||||
Value: map[string]any{
|
||||
|
|
@ -71,6 +82,14 @@ func (s *Service) ResolveScheme(ctx context.Context, rawURL string) core.Result
|
|||
return resolved
|
||||
}
|
||||
|
||||
if payload, ok := resolved.Value.(map[string]any); ok {
|
||||
if contentType, _ := payload["content_type"].(string); strings.EqualFold(contentType, "text/html") {
|
||||
if body, ok := payload["body"].(string); ok && strings.TrimSpace(body) != "" {
|
||||
return core.Result{Value: payload, OK: true}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
body := s.renderSchemeBody(route, resolved.Value)
|
||||
return core.Result{
|
||||
Value: map[string]any{
|
||||
|
|
@ -95,6 +114,15 @@ func (s *Service) renderSchemeBody(route string, value any) string {
|
|||
"</pre></main></body></html>"
|
||||
}
|
||||
|
||||
func (s *Service) renderStoreSearchPage(query string) string {
|
||||
safeQuery := html.EscapeString(query)
|
||||
return "<!doctype html><html><head><meta charset=\"utf-8\"><title>core://store</title><style>body{font:14px/1.5 ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,\"Segoe UI\",sans-serif;background:#0f172a;color:#e2e8f0;margin:0}header{padding:20px;border-bottom:1px solid #1e293b;background:linear-gradient(180deg,#111827,#0f172a)}main{padding:20px;display:grid;gap:16px}form{display:flex;gap:8px;flex-wrap:wrap;align-items:center}input{min-width:min(100%,420px);flex:1 1 320px;border-radius:12px;border:1px solid #334155;background:#020617;color:#e2e8f0;padding:12px 14px}button{border:0;border-radius:12px;background:#38bdf8;color:#082f49;padding:12px 16px;font-weight:700;cursor:pointer}section{background:#020617;border:1px solid #1e293b;border-radius:16px;padding:16px}ul{list-style:none;padding:0;margin:0;display:grid;gap:12px}.result{padding:12px;border:1px solid #1e293b;border-radius:12px;background:#0b1220}.meta{color:#94a3b8}.origin{font-weight:700;color:#7dd3fc}.bucket{color:#cbd5e1;font-size:12px;text-transform:uppercase;letter-spacing:.08em}.key{color:#f8fafc;font-weight:600}.value{white-space:pre-wrap;word-break:break-word;color:#e2e8f0}.empty{color:#94a3b8}code{background:#111827;border-radius:8px;padding:2px 6px}</style></head><body><header><strong>core://store</strong><div class=\"meta\">Search the in-memory storage scopes exposed by the preload shim. Query: <code>" +
|
||||
safeQuery +
|
||||
"</code></div></header><main><section><form method=\"get\" action=\"core://store\"><input name=\"q\" value=\"" +
|
||||
safeQuery +
|
||||
"\" placeholder=\"Search keys or values\"><button type=\"submit\">Search</button></form></section><section><div id=\"results\" class=\"meta\">Loading results...</div></section><script>(function(){const query=new URLSearchParams(location.search).get('q')||'';const needle=query.trim().toLowerCase();const scopes=globalThis.__coreStorageScopes||{};const results=[];for(const [origin,buckets] of Object.entries(scopes)){for(const [bucketName,bucket] of Object.entries(buckets||{})){for(const [key,rawValue] of Object.entries(bucket||{})){const value=String(rawValue);if(!needle||key.toLowerCase().includes(needle)||value.toLowerCase().includes(needle)){results.push({origin,bucket:bucketName,key,value});}}}}const root=document.getElementById('results');if(!root)return;if(!needle){root.innerHTML='<p class=\"meta\">Enter a search term to scan the captured storage scopes.</p>';return;}if(results.length===0){root.innerHTML='<p class=\"empty\">No matches found in the captured storage scopes.</p>';return;}root.innerHTML='<ul>'+results.map((item)=>'<li class=\"result\"><div class=\"origin\">'+item.origin+'</div><div class=\"bucket\">'+item.bucket+'</div><div class=\"key\">'+item.key+'</div><div class=\"value\">'+item.value+'</div></li>').join('')+'</ul>';})();</script></main></body></html>"
|
||||
}
|
||||
|
||||
func coalesce(values ...string) string {
|
||||
for _, value := range values {
|
||||
if strings.TrimSpace(value) != "" {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue