feat(frontend): wire app shell framework with dynamic providers
Some checks failed
Security Scan / security (push) Successful in 9s
Test / test (push) Failing after 52s

Replace the standalone IDE/Tray components with the framework shell from
core/gui/ui. The IDE frontend is now minimal routing config that uses:

- ApplicationFrameComponent as the HLCRF layout (header, sidebar, content, footer)
- ProviderHostComponent for dynamic custom element rendering via :provider route
- SystemTrayFrameComponent for the 380x480 tray panel

Go side: add GET /api/v1/providers endpoint (ProvidersAPI) that returns all
registered providers from the Registry plus runtime providers from the
RuntimeManager. The Angular frontend calls this on startup to populate
navigation and load custom elements.

Also bumps @angular/build and @angular/cli to 21.x to match @angular/core.

Co-Authored-By: Virgil <virgil@lethean.io>
This commit is contained in:
Snider 2026-03-14 12:41:55 +00:00
parent fe90224de6
commit b9500bf866
7 changed files with 1803 additions and 1347 deletions

File diff suppressed because it is too large Load diff

View file

@ -40,8 +40,8 @@
"zone.js": "~0.15.0"
},
"devDependencies": {
"@angular/build": "^20.3.6",
"@angular/cli": "^20.3.15",
"@angular/build": "^21.1.2",
"@angular/cli": "^21.1.3",
"@angular/compiler-cli": "^20.3.0",
"@types/express": "^5.0.1",
"@types/jasmine": "~5.1.0",

View file

@ -1,13 +1,21 @@
import { ApplicationConfig, provideBrowserGlobalErrorListeners, provideZoneChangeDetection } from '@angular/core';
// SPDX-Licence-Identifier: EUPL-1.2
import {
ApplicationConfig,
CUSTOM_ELEMENTS_SCHEMA,
provideBrowserGlobalErrorListeners,
provideZoneChangeDetection,
} from '@angular/core';
import { provideRouter } from '@angular/router';
import { provideClientHydration, withEventReplay } from '@angular/platform-browser';
import { routes } from './app.routes';
import { provideClientHydration, withEventReplay } from '@angular/platform-browser';
export const appConfig: ApplicationConfig = {
providers: [
provideBrowserGlobalErrorListeners(),
provideZoneChangeDetection({ eventCoalescing: true }),
provideRouter(routes), provideClientHydration(withEventReplay())
]
provideRouter(routes),
provideClientHydration(withEventReplay()),
],
};

View file

@ -1,17 +1,26 @@
// SPDX-Licence-Identifier: EUPL-1.2
import { Routes } from '@angular/router';
import { TrayComponent } from './pages/tray/tray.component';
import { IdeComponent } from './pages/ide/ide.component';
import {
ApplicationFrameComponent,
ProviderHostComponent,
SystemTrayFrameComponent,
} from '@core/gui-ui';
export const routes: Routes = [
// System tray panel - standalone compact UI
{ path: 'tray', component: TrayComponent },
// System tray panel — standalone compact UI (380x480 frameless)
{ path: 'tray', component: SystemTrayFrameComponent },
// Full IDE interface
{ path: 'ide', component: IdeComponent },
// Main application frame with HLCRF layout
{
path: '',
component: ApplicationFrameComponent,
children: [
// Dynamic provider rendering via route parameter
{ path: ':provider', component: ProviderHostComponent },
// Default to tray for the root (tray panel is the default view)
{ path: '', redirectTo: 'tray', pathMatch: 'full' },
// Catch-all
{ path: '**', redirectTo: 'tray' },
// Default to process provider (first registered)
{ path: '', redirectTo: 'process', pathMatch: 'full' },
],
},
];

View file

@ -19,6 +19,12 @@
"paths": {
"@bindings/*": [
"bindings/*"
],
"@core/gui-ui": [
"../../gui/ui/src/index.ts"
],
"@core/gui-ui/*": [
"../../gui/ui/src/*"
]
}
},

View file

@ -81,6 +81,10 @@ func main() {
// ── Runtime Provider Manager ──────────────────────────────
rm := NewRuntimeManager(engine)
// ── Providers API ─────────────────────────────────────────
// Exposes GET /api/v1/providers for the Angular frontend
engine.Register(NewProvidersAPI(reg, rm))
// ── Core framework ─────────────────────────────────────────
c, err := core.New(
core.WithName("ws", func(c *core.Core) (any, error) {

98
providers.go Normal file
View file

@ -0,0 +1,98 @@
// SPDX-Licence-Identifier: EUPL-1.2
package main
import (
"net/http"
"forge.lthn.ai/core/api/pkg/provider"
"github.com/gin-gonic/gin"
)
// ProvidersAPI exposes registered provider information via GET /api/v1/providers.
// The Angular frontend uses this endpoint to discover providers and load their
// custom elements dynamically.
type ProvidersAPI struct {
registry *provider.Registry
runtime *RuntimeManager
}
func NewProvidersAPI(reg *provider.Registry, rm *RuntimeManager) *ProvidersAPI {
return &ProvidersAPI{registry: reg, runtime: rm}
}
func (p *ProvidersAPI) Name() string { return "providers-api" }
func (p *ProvidersAPI) BasePath() string { return "/api/v1/providers" }
func (p *ProvidersAPI) RegisterRoutes(rg *gin.RouterGroup) {
rg.GET("", p.list)
}
// list godoc
//
// @Summary List registered providers
// @Description Returns all registered providers with their capabilities
// @Tags providers
// @Produce json
// @Success 200 {object} providersResponse
// @Router /api/v1/providers [get]
func (p *ProvidersAPI) list(c *gin.Context) {
registryInfo := p.registry.Info()
runtimeInfo := p.runtime.List()
// Merge runtime provider info with registry info
providers := make([]providerDTO, 0, len(registryInfo)+len(runtimeInfo))
for _, info := range registryInfo {
dto := providerDTO{
Name: info.Name,
BasePath: info.BasePath,
Channels: info.Channels,
Status: "active",
}
if info.Element != nil {
dto.Element = &elementDTO{
Tag: info.Element.Tag,
Source: info.Element.Source,
}
}
providers = append(providers, dto)
}
// Add runtime providers not already in registry
for _, ri := range runtimeInfo {
found := false
for _, p := range providers {
if p.Name == ri.Code {
found = true
break
}
}
if !found {
providers = append(providers, providerDTO{
Name: ri.Code,
BasePath: ri.Namespace,
Status: "active",
})
}
}
c.JSON(http.StatusOK, providersResponse{Providers: providers})
}
type providersResponse struct {
Providers []providerDTO `json:"providers"`
}
type providerDTO struct {
Name string `json:"name"`
BasePath string `json:"basePath"`
Status string `json:"status,omitempty"`
Element *elementDTO `json:"element,omitempty"`
Channels []string `json:"channels,omitempty"`
}
type elementDTO struct {
Tag string `json:"tag"`
Source string `json:"source"`
}