Skip to content

Commit 8d985f6

Browse files
refactor: semantic port to AffineScript (#46)
Automated semantic porting.
1 parent 59e1b1b commit 8d985f6

4 files changed

Lines changed: 485 additions & 12 deletions

File tree

ui/src/App.affine

Lines changed: 385 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,389 @@
11
// SPDX-License-Identifier: MPL-2.0
2-
// SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell
3-
// Ported via Harvard Engine bulk-processor
2+
// Ported via Harvard Engine (Semantic pass)
43

54
module App;
65

7-
// TODO: Complete semantic implementation
6+
struct apiState {
7+
| Idle
8+
| Loading
9+
| Loaded(string)
10+
| Failed(string)
11+
12+
struct scriptAction { {
13+
id: string,
14+
label: string,
15+
detail: string,
16+
}
17+
18+
struct model { {
19+
route: CadreTeaRouter.route,
20+
harCategory: string,
21+
harTarget: string,
22+
harStatus: apiState,
23+
harTargets: apiState,
24+
harRoute: apiState,
25+
scriptRun: apiState,
26+
}
27+
28+
struct msg {
29+
| SyncRoute
30+
| Navigate(CadreTeaRouter.route)
31+
| SetHarCategory(string)
32+
| SetHarTarget(string)
33+
| FetchHarStatus
34+
| FetchHarTargets
35+
| RunHarRoute
36+
| RunScript(string)
37+
| HarStatusResult(result<string, Tea.Http.error>)
38+
| HarTargetsResult(result<string, Tea.Http.error>)
39+
| HarRouteResult(result<string, Tea.Http.error>)
40+
| ScriptResult(result<string, Tea.Http.error>)
41+
42+
fn scriptActions: array<scriptAction> = [
43+
{
44+
id: "wiki_audit",
45+
label: "Wiki Audit",
46+
detail: "Run scripts/wiki-audit.sh",
47+
},
48+
{
49+
id: "project_tabs_audit",
50+
label: "Project Tabs Audit",
51+
detail: "Run scripts/project-tabs-audit.sh",
52+
},
53+
{
54+
id: "branch_protection_dry_run",
55+
label: "Branch Protection (dry-run)",
56+
detail: "Run scripts/branch-protection-apply.sh --dry-run",
57+
},
58+
{
59+
id: "md_to_adoc",
60+
label: "MD to ADOC",
61+
detail: "Run scripts/md_to_adoc_converter.sh",
62+
},
63+
{
64+
id: "standardize_readmes",
65+
label: "Standardize READMEs",
66+
detail: "Run scripts/standardize_readmes.sh",
67+
},
68+
{
69+
id: "audit_scripts",
70+
label: "Audit Scripts",
71+
detail: "Run scripts/audit_script.sh",
72+
},
73+
{
74+
id: "verify",
75+
label: "Verify",
76+
detail: "Run scripts/verify.sh",
77+
},
78+
{
79+
id: "gh_cli",
80+
label: "Use GH CLI",
81+
detail: "Run scripts/USE-GH-CLI.sh",
82+
},
83+
]
84+
85+
fn init = (): (model, Tea.Cmd.t<msg>) => {
86+
fn route = CadreTeaRouter.currentRoute()
87+
(
88+
{
89+
route,
90+
harCategory: "filesystem",
91+
harTarget: "",
92+
harStatus: Idle,
93+
harTargets: Idle,
94+
harRoute: Idle,
95+
scriptRun: Idle,
96+
},
97+
Tea.Cmd.batch(list{Tea.Cmd.msg(FetchHarStatus), Tea.Cmd.msg(FetchHarTargets)}),
98+
)
99+
}
100+
101+
fn failWithHttpError = (httpError: Tea.Http.error): apiState => Failed(Tea.Http.errorToString(httpError))
102+
103+
fn update = (model: model, msg: msg): (model, Tea.Cmd.t<msg>) => {
104+
switch msg {
105+
| SyncRoute => ({...model, route: CadreTeaRouter.currentRoute()}, Tea.Cmd.none)
106+
| Navigate(route) => {
107+
if route == model.route {
108+
(model, Tea.Cmd.none)
109+
} else {
110+
({...model, route}, CadreTeaRouter.navigate(route, SyncRoute))
111+
}
112+
}
113+
| SetHarCategory(category) => ({...model, harCategory: category}, Tea.Cmd.none)
114+
| SetHarTarget(target) => ({...model, harTarget: target}, Tea.Cmd.none)
115+
| FetchHarStatus =>
116+
(
117+
{...model, harStatus: Loading},
118+
HarApi.fetchStatus(result => HarStatusResult(result)),
119+
)
120+
| FetchHarTargets =>
121+
(
122+
{...model, harTargets: Loading},
123+
HarApi.fetchTargets(result => HarTargetsResult(result)),
124+
)
125+
| RunHarRoute => {
126+
fn category = model.harCategory->String.trim
127+
if category == "" {
128+
({...model, harRoute: Failed("Category is required.")}, Tea.Cmd.none)
129+
} else {
130+
fn target =
131+
switch model.harTarget->String.trim {
132+
| "" => None
133+
| value => Some(value)
134+
}
135+
(
136+
{...model, harRoute: Loading},
137+
HarApi.routeCategory(~category, ~target, ~toMsg=result => HarRouteResult(result)),
138+
)
139+
}
140+
}
141+
| RunScript(action) => (
142+
{...model, scriptRun: Loading},
143+
HarApi.runScript(~action, ~toMsg=result => ScriptResult(result)),
144+
)
145+
| HarStatusResult(result) => {
146+
switch result {
147+
| Ok(body) => ({...model, harStatus: Loaded(body)}, Tea.Cmd.none)
148+
| Error(httpError) => ({...model, harStatus: failWithHttpError(httpError)}, Tea.Cmd.none)
149+
}
150+
}
151+
| HarTargetsResult(result) => {
152+
switch result {
153+
| Ok(body) => ({...model, harTargets: Loaded(body)}, Tea.Cmd.none)
154+
| Error(httpError) => ({...model, harTargets: failWithHttpError(httpError)}, Tea.Cmd.none)
155+
}
156+
}
157+
| HarRouteResult(result) => {
158+
switch result {
159+
| Ok(body) => ({...model, harRoute: Loaded(body)}, Tea.Cmd.none)
160+
| Error(httpError) => ({...model, harRoute: failWithHttpError(httpError)}, Tea.Cmd.none)
161+
}
162+
}
163+
| ScriptResult(result) => {
164+
switch result {
165+
| Ok(body) => ({...model, scriptRun: Loaded(body)}, Tea.Cmd.none)
166+
| Error(httpError) => ({...model, scriptRun: failWithHttpError(httpError)}, Tea.Cmd.none)
167+
}
168+
}
169+
}
170+
}
171+
172+
fn viewApiState = (state: apiState): Tea.Html.t<msg> => {
173+
open Tea.Html
174+
switch state {
175+
| Idle => div(list{Attrs.class_("status")}, list{text("Idle.")})
176+
| Loading => div(list{Attrs.class_("status loading")}, list{text("Running...")})
177+
| Failed(error) => div(list{Attrs.class_("status err")}, list{text(error)})
178+
| Loaded(payload) =>
179+
div(
180+
list{},
181+
list{
182+
div(list{Attrs.class_("status ok")}, list{text("Completed")}),
183+
pre(list{Attrs.class_("output")}, list{text(payload)}),
184+
},
185+
)
186+
}
187+
}
188+
189+
fn viewTab = (activeRoute: CadreTeaRouter.route, route: CadreTeaRouter.route): Tea.Html.t<msg> => {
190+
open Tea.Html
191+
fn className = if activeRoute == route { "tab active" } else { "tab" }
192+
button(list{Attrs.class_(className), Events.onClick(Navigate(route))}, list{text(CadreTeaRouter.label(route))})
193+
}
194+
195+
fn viewScriptAction = (action: scriptAction): Tea.Html.t<msg> => {
196+
open Tea.Html
197+
li(
198+
list{},
199+
list{
200+
div(
201+
list{Attrs.class_("row")},
202+
list{
203+
button(
204+
list{Attrs.class_("button secondary"), Events.onClick(RunScript(action.id))},
205+
list{text(action.label)},
206+
),
207+
span(list{Attrs.class_("muted")}, list{text(action.detail)}),
208+
},
209+
),
210+
},
211+
)
212+
}
213+
214+
fn viewDashboard = (model: model): Tea.Html.t<msg> => {
215+
open Tea.Html
216+
fragment(
217+
list{
218+
div(
219+
list{Attrs.class_("panel")},
220+
list{
221+
h2(list{}, list{text("Hybrid Automation Router Status")}),
222+
p(list{Attrs.class_("muted")}, list{text("Live status from HAR CLI.")}),
223+
div(
224+
list{Attrs.class_("row")},
225+
list{
226+
button(list{Attrs.class_("button"), Events.onClick(FetchHarStatus)}, list{text("Refresh Status")}),
227+
button(
228+
list{Attrs.class_("button secondary"), Events.onClick(FetchHarTargets)},
229+
list{text("Load Targets")},
230+
),
231+
},
232+
),
233+
viewApiState(model.harStatus),
234+
},
235+
),
236+
div(
237+
list{Attrs.class_("panel")},
238+
list{
239+
h2(list{}, list{text("Current HAR Targets")}),
240+
p(list{Attrs.class_("muted")}, list{text("Resolved from the active HAR command source.")}),
241+
viewApiState(model.harTargets),
242+
},
243+
),
244+
div(
245+
list{Attrs.class_("panel")},
246+
list{
247+
h2(list{}, list{text("Latest Script Output")}),
248+
p(
249+
list{Attrs.class_("muted")},
250+
list{text("Run script actions from Operations tab. Output appears here and in Operations.")},
251+
),
252+
viewApiState(model.scriptRun),
253+
},
254+
),
255+
},
256+
)
257+
}
258+
259+
fn viewOperations = (model: model): Tea.Html.t<msg> => {
260+
open Tea.Html
261+
fn actionNodes = scriptActions->Array.map(viewScriptAction)->List.fromArray
262+
263+
fragment(
264+
list{
265+
div(
266+
list{Attrs.class_("panel")},
267+
list{
268+
h2(list{}, list{text("Script Operations")}),
269+
p(
270+
list{Attrs.class_("muted")},
271+
list{text("These actions run allowlisted scripts from git-scripts/scripts.")},
272+
),
273+
ul(list{Attrs.class_("actions")}, actionNodes),
274+
},
275+
),
276+
div(
277+
list{Attrs.class_("panel")},
278+
list{
279+
h2(list{}, list{text("Operation Result")}),
280+
viewApiState(model.scriptRun),
281+
},
282+
),
283+
},
284+
)
285+
}
286+
287+
fn viewHybridRouter = (model: model): Tea.Html.t<msg> => {
288+
open Tea.Html
289+
fragment(
290+
list{
291+
div(
292+
list{Attrs.class_("panel")},
293+
list{
294+
h2(list{}, list{text("Route A Category")}),
295+
p(
296+
list{Attrs.class_("muted")},
297+
list{text("Send a category to HAR and inspect the routing decision payload.")},
298+
),
299+
div(
300+
list{Attrs.class_("row")},
301+
list{
302+
input(
303+
list{
304+
Attrs.class_("input"),
305+
Attrs.placeholder("category, e.g. filesystem"),
306+
Attrs.value(model.harCategory),
307+
Events.onInput(value => SetHarCategory(value)),
308+
},
309+
list{},
310+
),
311+
input(
312+
list{
313+
Attrs.class_("input"),
314+
Attrs.placeholder("optional target hint"),
315+
Attrs.value(model.harTarget),
316+
Events.onInput(value => SetHarTarget(value)),
317+
},
318+
list{},
319+
),
320+
},
321+
),
322+
div(
323+
list{Attrs.class_("row")},
324+
list{
325+
button(list{Attrs.class_("button"), Events.onClick(RunHarRoute)}, list{text("Route Event")}),
326+
},
327+
),
328+
viewApiState(model.harRoute),
329+
},
330+
),
331+
div(
332+
list{Attrs.class_("panel")},
333+
list{
334+
h2(list{}, list{text("HAR Status Snapshot")}),
335+
viewApiState(model.harStatus),
336+
},
337+
),
338+
},
339+
)
340+
}
341+
342+
fn view = (model: model): Tea.Html.t<msg> => {
343+
open Tea.Html
344+
div(
345+
list{Attrs.class_("shell")},
346+
list{
347+
header(
348+
list{},
349+
list{
350+
h1(list{Attrs.class_("title")}, list{text("Git Scripts Cadre-TEA Router UI")}),
351+
p(
352+
list{Attrs.class_("subtitle")},
353+
list{
354+
text(
355+
"ReScript-TEA control plane with hybrid-automation-router + allowlisted script execution.",
356+
),
357+
},
358+
),
359+
},
360+
),
361+
nav(
362+
list{Attrs.class_("tabs")},
363+
list{
364+
viewTab(model.route, CadreTeaRouter.Dashboard),
365+
viewTab(model.route, CadreTeaRouter.Operations),
366+
viewTab(model.route, CadreTeaRouter.HybridRouter),
367+
},
368+
),
369+
switch model.route {
370+
| CadreTeaRouter.Dashboard => viewDashboard(model)
371+
| CadreTeaRouter.Operations => viewOperations(model)
372+
| CadreTeaRouter.HybridRouter => viewHybridRouter(model)
373+
},
374+
},
375+
)
376+
}
377+
378+
fn subscriptions = (_model: model): Tea.Sub.t<msg> => CadreTeaRouter.subscriptions(SyncRoute)
379+
380+
fn start = () => {
381+
Tea.standardProgram(
382+
~init,
383+
~update,
384+
~view,
385+
~subscriptions,
386+
(),
387+
)
388+
}
389+

0 commit comments

Comments
 (0)