@bespokeagentics/create-microdots-host 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +202 -0
- package/package.json +26 -0
- package/src/cli.ts +50 -0
- package/src/index.ts +215 -0
- package/templates/base/README.md +49 -0
- package/templates/base/_gitignore +7 -0
- package/templates/base/host/index.html +33 -0
- package/templates/base/host/package.json +10 -0
- package/templates/base/host/public/themes/neutral-dark.css +109 -0
- package/templates/base/host/public/themes/neutral.css +109 -0
- package/templates/base/host/src/main.ts +202 -0
- package/templates/base/host/src/topology.test.ts +42 -0
- package/templates/base/host/topology.json +84 -0
- package/templates/base/host/vite.base.ts +41 -0
- package/templates/base/microdots/pulse/package.json +20 -0
- package/templates/base/microdots/pulse/service/handlers.ts +36 -0
- package/templates/base/microdots/pulse/service/main.ts +30 -0
- package/templates/base/microdots/pulse/src/app.ts +275 -0
- package/templates/base/microdots/pulse/src/client.ts +14 -0
- package/templates/base/microdots/pulse/src/contract.ts +32 -0
- package/templates/base/microdots/pulse/src/element.ts +24 -0
- package/templates/base/microdots/pulse/src/entry.ts +1 -0
- package/templates/base/microdots/pulse/src/surface.ts +30 -0
- package/templates/base/microdots/pulse/src/tokens.css +30 -0
- package/templates/base/microdots/scope-picker/package.json +20 -0
- package/templates/base/microdots/scope-picker/service/handlers.ts +15 -0
- package/templates/base/microdots/scope-picker/service/main.ts +30 -0
- package/templates/base/microdots/scope-picker/src/app.ts +256 -0
- package/templates/base/microdots/scope-picker/src/client.ts +18 -0
- package/templates/base/microdots/scope-picker/src/contract.ts +27 -0
- package/templates/base/microdots/scope-picker/src/element.ts +21 -0
- package/templates/base/microdots/scope-picker/src/entry.ts +1 -0
- package/templates/base/microdots/scope-picker/src/surface.ts +23 -0
- package/templates/base/microdots/scope-picker/src/tokens.css +30 -0
- package/templates/base/scripts/build-output.test.ts +71 -0
- package/templates/base/scripts/build-output.ts +28 -0
- package/templates/base/scripts/build.ts +37 -0
- package/templates/base/scripts/check-tokens.ts +63 -0
- package/templates/base/scripts/dev.ts +78 -0
- package/templates/base/scripts/discover.test.ts +133 -0
- package/templates/base/scripts/discover.ts +137 -0
- package/templates/base/scripts/dot-vite.ts +28 -0
- package/templates/base/scripts/emit-manifests.ts +154 -0
- package/templates/base/tsconfig.json +18 -0
- package/templates/base/types.d.ts +9 -0
- package/templates/base/vitest.config.ts +8 -0
- package/templates/plain/host/src/styles.css +46 -0
- package/templates/plain/host/vite.config.template.ts +4 -0
- package/templates/plain/microdots/pulse/src/styles.css +17 -0
- package/templates/plain/microdots/pulse/vite.config.template.ts +4 -0
- package/templates/plain/microdots/scope-picker/src/styles.css +19 -0
- package/templates/plain/microdots/scope-picker/vite.config.template.ts +4 -0
- package/templates/plain/package.json +39 -0
- package/templates/tailwind/host/src/styles.css +28 -0
- package/templates/tailwind/host/vite.config.template.ts +5 -0
- package/templates/tailwind/microdots/pulse/src/styles.css +24 -0
- package/templates/tailwind/microdots/pulse/vite.config.template.ts +5 -0
- package/templates/tailwind/microdots/scope-picker/src/styles.css +25 -0
- package/templates/tailwind/microdots/scope-picker/vite.config.template.ts +5 -0
- package/templates/tailwind/package.json +41 -0
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
import { Duration, Effect, Schema as S, Stream } from 'effect'
|
|
2
|
+
import { Command, Port, Subscription } from 'foldkit'
|
|
3
|
+
import type { Html, HtmlBuilder } from 'foldkit/html'
|
|
4
|
+
import { m } from 'foldkit/message'
|
|
5
|
+
import { makeElement } from 'foldkit/runtime'
|
|
6
|
+
import { ts } from 'foldkit/schema'
|
|
7
|
+
|
|
8
|
+
import { PulseClient, pulseClientLive } from './client.ts'
|
|
9
|
+
import { Heartbeat, HeartbeatObserved } from './contract.ts'
|
|
10
|
+
|
|
11
|
+
export const POLL_INTERVAL_MS = 5_000
|
|
12
|
+
|
|
13
|
+
const Loading = ts('Loading')
|
|
14
|
+
const Ready = ts('Ready', { value: Heartbeat, stale: S.Boolean })
|
|
15
|
+
const Failed = ts('Failed', { error: S.String, last: S.UndefinedOr(Heartbeat) })
|
|
16
|
+
const Unauthorized = ts('Unauthorized')
|
|
17
|
+
const RemoteHeartbeat = S.Union([Loading, Ready, Failed, Unauthorized])
|
|
18
|
+
type RemoteHeartbeat = typeof RemoteHeartbeat.Type
|
|
19
|
+
|
|
20
|
+
const Model = S.Struct({
|
|
21
|
+
heartbeat: RemoteHeartbeat,
|
|
22
|
+
scopeId: S.String,
|
|
23
|
+
apiUrl: S.String,
|
|
24
|
+
pollIntervalMs: S.Number,
|
|
25
|
+
polls: S.Number,
|
|
26
|
+
})
|
|
27
|
+
type Model = typeof Model.Type
|
|
28
|
+
|
|
29
|
+
const PollTicked = m('PollTicked')
|
|
30
|
+
const HeartbeatReceived = m('HeartbeatReceived', { heartbeat: Heartbeat })
|
|
31
|
+
const HeartbeatFailed = m('HeartbeatFailed', { error: S.String })
|
|
32
|
+
const HeartbeatPublished = m('HeartbeatPublished')
|
|
33
|
+
const ScopeChanged = m('ScopeChanged', { scopeId: S.String })
|
|
34
|
+
const RefreshRequested = m('RefreshRequested')
|
|
35
|
+
const Message = S.Union([
|
|
36
|
+
PollTicked,
|
|
37
|
+
HeartbeatReceived,
|
|
38
|
+
HeartbeatFailed,
|
|
39
|
+
HeartbeatPublished,
|
|
40
|
+
ScopeChanged,
|
|
41
|
+
RefreshRequested,
|
|
42
|
+
])
|
|
43
|
+
type Message = typeof Message.Type
|
|
44
|
+
|
|
45
|
+
export const ports = {
|
|
46
|
+
inbound: { scopeChanged: Port.inbound(S.String) },
|
|
47
|
+
outbound: { heartbeatObserved: Port.outbound(HeartbeatObserved) },
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const FetchHeartbeat = Command.define('FetchHeartbeat', {
|
|
51
|
+
args: { scopeId: S.String },
|
|
52
|
+
messages: [HeartbeatReceived, HeartbeatFailed],
|
|
53
|
+
execute: ({ scopeId }) =>
|
|
54
|
+
Effect.gen(function* () {
|
|
55
|
+
const client = yield* PulseClient
|
|
56
|
+
const heartbeat = yield* client.GetHeartbeat({ scopeId })
|
|
57
|
+
return HeartbeatReceived({ heartbeat })
|
|
58
|
+
}).pipe(
|
|
59
|
+
Effect.catch(error =>
|
|
60
|
+
Effect.succeed(
|
|
61
|
+
HeartbeatFailed({
|
|
62
|
+
error:
|
|
63
|
+
error._tag === 'HeartbeatUnavailable'
|
|
64
|
+
? error.reason
|
|
65
|
+
: `could not reach the service (${error._tag})`,
|
|
66
|
+
}),
|
|
67
|
+
),
|
|
68
|
+
),
|
|
69
|
+
),
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
const PublishHeartbeat = Command.define('PublishHeartbeat', {
|
|
73
|
+
args: { id: S.String, sequence: S.Number, scopeId: S.String },
|
|
74
|
+
messages: [HeartbeatPublished],
|
|
75
|
+
execute: payload =>
|
|
76
|
+
Port.emit(ports.outbound.heartbeatObserved, payload).pipe(
|
|
77
|
+
Effect.as(HeartbeatPublished()),
|
|
78
|
+
),
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
const refreshing = (remote: RemoteHeartbeat): RemoteHeartbeat =>
|
|
82
|
+
remote._tag === 'Ready' ? Ready({ value: remote.value, stale: true }) : remote
|
|
83
|
+
|
|
84
|
+
const lastGood = (remote: RemoteHeartbeat): Heartbeat | undefined =>
|
|
85
|
+
remote._tag === 'Ready'
|
|
86
|
+
? remote.value
|
|
87
|
+
: remote._tag === 'Failed'
|
|
88
|
+
? remote.last
|
|
89
|
+
: undefined
|
|
90
|
+
|
|
91
|
+
type UpdateResult = readonly [
|
|
92
|
+
Model,
|
|
93
|
+
ReadonlyArray<Command.Command<Message, never, PulseClient>>,
|
|
94
|
+
]
|
|
95
|
+
|
|
96
|
+
const update = (model: Model, message: Message): UpdateResult => {
|
|
97
|
+
switch (message._tag) {
|
|
98
|
+
case 'PollTicked':
|
|
99
|
+
case 'RefreshRequested':
|
|
100
|
+
return [
|
|
101
|
+
{ ...model, heartbeat: refreshing(model.heartbeat) },
|
|
102
|
+
[FetchHeartbeat({ scopeId: model.scopeId })],
|
|
103
|
+
]
|
|
104
|
+
case 'HeartbeatReceived':
|
|
105
|
+
return [
|
|
106
|
+
{
|
|
107
|
+
...model,
|
|
108
|
+
heartbeat: Ready({ value: message.heartbeat, stale: false }),
|
|
109
|
+
polls: model.polls + 1,
|
|
110
|
+
},
|
|
111
|
+
[
|
|
112
|
+
PublishHeartbeat({
|
|
113
|
+
id: message.heartbeat.id,
|
|
114
|
+
sequence: message.heartbeat.sequence,
|
|
115
|
+
scopeId: message.heartbeat.scopeId,
|
|
116
|
+
}),
|
|
117
|
+
],
|
|
118
|
+
]
|
|
119
|
+
case 'HeartbeatFailed':
|
|
120
|
+
return [
|
|
121
|
+
{
|
|
122
|
+
...model,
|
|
123
|
+
heartbeat: Failed({
|
|
124
|
+
error: message.error,
|
|
125
|
+
last: lastGood(model.heartbeat),
|
|
126
|
+
}),
|
|
127
|
+
},
|
|
128
|
+
[],
|
|
129
|
+
]
|
|
130
|
+
case 'ScopeChanged':
|
|
131
|
+
return [
|
|
132
|
+
{
|
|
133
|
+
...model,
|
|
134
|
+
scopeId: message.scopeId,
|
|
135
|
+
heartbeat: refreshing(model.heartbeat),
|
|
136
|
+
},
|
|
137
|
+
[FetchHeartbeat({ scopeId: message.scopeId })],
|
|
138
|
+
]
|
|
139
|
+
case 'HeartbeatPublished':
|
|
140
|
+
return [model, []]
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const subscriptions = Subscription.make<Model, Message, PulseClient>()(
|
|
145
|
+
entry => ({
|
|
146
|
+
poll: entry(
|
|
147
|
+
{ pollIntervalMs: S.Number },
|
|
148
|
+
{
|
|
149
|
+
modelToDependencies: model => ({
|
|
150
|
+
pollIntervalMs: model.pollIntervalMs,
|
|
151
|
+
}),
|
|
152
|
+
dependenciesToStream: ({ pollIntervalMs }) =>
|
|
153
|
+
Stream.map(Stream.tick(Duration.millis(pollIntervalMs)), () =>
|
|
154
|
+
PollTicked(),
|
|
155
|
+
),
|
|
156
|
+
},
|
|
157
|
+
),
|
|
158
|
+
hostScope: Port.subscription(ports.inbound.scopeChanged, scopeId =>
|
|
159
|
+
ScopeChanged({ scopeId }),
|
|
160
|
+
),
|
|
161
|
+
}),
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
const field = (h: HtmlBuilder<Message>, label: string, value: string): Html =>
|
|
165
|
+
h.div(
|
|
166
|
+
[h.Class('pulse__field')],
|
|
167
|
+
[
|
|
168
|
+
h.span([h.Class('pulse__label')], [label]),
|
|
169
|
+
h.span([h.Class('pulse__value')], [value]),
|
|
170
|
+
],
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
const heartbeatFields = (
|
|
174
|
+
h: HtmlBuilder<Message>,
|
|
175
|
+
heartbeat: Heartbeat,
|
|
176
|
+
): ReadonlyArray<Html> => [
|
|
177
|
+
field(h, 'sequence', String(heartbeat.sequence)),
|
|
178
|
+
field(h, 'scope echoed back', heartbeat.scopeId),
|
|
179
|
+
field(h, 'observed at', heartbeat.observedAt),
|
|
180
|
+
]
|
|
181
|
+
|
|
182
|
+
const body = (h: HtmlBuilder<Message>, model: Model): ReadonlyArray<Html> => {
|
|
183
|
+
const remote = model.heartbeat
|
|
184
|
+
switch (remote._tag) {
|
|
185
|
+
case 'Loading':
|
|
186
|
+
return [h.p([h.Class('microdot-note')], ['Loading first heartbeat…'])]
|
|
187
|
+
case 'Ready':
|
|
188
|
+
return [
|
|
189
|
+
h.p(
|
|
190
|
+
[h.Class('microdot-state')],
|
|
191
|
+
[remote.stale ? 'Refreshing' : 'Ready'],
|
|
192
|
+
),
|
|
193
|
+
...heartbeatFields(h, remote.value),
|
|
194
|
+
]
|
|
195
|
+
case 'Failed':
|
|
196
|
+
return [
|
|
197
|
+
h.p([h.Class('microdot-state microdot-state--error')], ['Failed']),
|
|
198
|
+
h.p([h.Class('microdot-note')], [remote.error]),
|
|
199
|
+
...(remote.last === undefined
|
|
200
|
+
? [
|
|
201
|
+
h.p(
|
|
202
|
+
[h.Class('microdot-note')],
|
|
203
|
+
['No previous value to fall back on.'],
|
|
204
|
+
),
|
|
205
|
+
]
|
|
206
|
+
: [
|
|
207
|
+
h.p([h.Class('microdot-note')], ['Showing the last good value.']),
|
|
208
|
+
...heartbeatFields(h, remote.last),
|
|
209
|
+
]),
|
|
210
|
+
]
|
|
211
|
+
case 'Unauthorized':
|
|
212
|
+
return [h.p([h.Class('microdot-state')], ['Unauthorized'])]
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
const view = (model: Model, h: HtmlBuilder<Message>): Html =>
|
|
217
|
+
h.article(
|
|
218
|
+
[h.Class('starter-card pulse')],
|
|
219
|
+
[
|
|
220
|
+
h.header(
|
|
221
|
+
[],
|
|
222
|
+
[
|
|
223
|
+
h.p([h.Class('microdot-overline')], ['Independent service · 3202']),
|
|
224
|
+
h.h2([], ['Pulse readout']),
|
|
225
|
+
h.p(
|
|
226
|
+
[h.Class('microdot-note')],
|
|
227
|
+
[
|
|
228
|
+
'Receives scope-id live and still polls when no broker is present.',
|
|
229
|
+
],
|
|
230
|
+
),
|
|
231
|
+
],
|
|
232
|
+
),
|
|
233
|
+
h.div([h.Class('starter-card__body')], body(h, model)),
|
|
234
|
+
h.footer(
|
|
235
|
+
[],
|
|
236
|
+
[
|
|
237
|
+
h.span(
|
|
238
|
+
[],
|
|
239
|
+
[`scope-id ${model.scopeId} · successful polls ${model.polls}`],
|
|
240
|
+
),
|
|
241
|
+
h.button(
|
|
242
|
+
[h.Class('microdot-action'), h.OnClick(RefreshRequested())],
|
|
243
|
+
['Refresh'],
|
|
244
|
+
),
|
|
245
|
+
],
|
|
246
|
+
),
|
|
247
|
+
],
|
|
248
|
+
)
|
|
249
|
+
|
|
250
|
+
export const makePulseProgram = (
|
|
251
|
+
container: HTMLElement,
|
|
252
|
+
attributes: Readonly<Record<string, string>>,
|
|
253
|
+
) => {
|
|
254
|
+
const apiUrl = attributes['api-url'] ?? ''
|
|
255
|
+
const scopeId = attributes['scope-id'] ?? 'general'
|
|
256
|
+
return makeElement<Model, Message, PulseClient, never, typeof ports>({
|
|
257
|
+
Model,
|
|
258
|
+
container,
|
|
259
|
+
ports,
|
|
260
|
+
resources: pulseClientLive(apiUrl),
|
|
261
|
+
init: () => [
|
|
262
|
+
{
|
|
263
|
+
heartbeat: Loading(),
|
|
264
|
+
scopeId,
|
|
265
|
+
apiUrl,
|
|
266
|
+
pollIntervalMs: POLL_INTERVAL_MS,
|
|
267
|
+
polls: 0,
|
|
268
|
+
},
|
|
269
|
+
[FetchHeartbeat({ scopeId })],
|
|
270
|
+
],
|
|
271
|
+
update,
|
|
272
|
+
view,
|
|
273
|
+
subscriptions,
|
|
274
|
+
})
|
|
275
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { Context } from 'effect'
|
|
2
|
+
import type { Layer } from 'effect'
|
|
3
|
+
import type { RpcClient, RpcClientError } from 'effect/unstable/rpc'
|
|
4
|
+
import { makeRpcClientLive } from '@bespokeagentics/microdots-runtime'
|
|
5
|
+
|
|
6
|
+
import { PulseRpcs } from './contract.ts'
|
|
7
|
+
|
|
8
|
+
export class PulseClient extends Context.Service<
|
|
9
|
+
PulseClient,
|
|
10
|
+
RpcClient.RpcClient<PulseRpcs, RpcClientError.RpcClientError>
|
|
11
|
+
>()('PulseClient') {}
|
|
12
|
+
|
|
13
|
+
export const pulseClientLive: (baseUrl: string) => Layer.Layer<PulseClient> =
|
|
14
|
+
makeRpcClientLive(PulseClient, PulseRpcs)
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { Schema as S } from 'effect'
|
|
2
|
+
import { Rpc, RpcGroup } from 'effect/unstable/rpc'
|
|
3
|
+
|
|
4
|
+
export const Heartbeat = S.Struct({
|
|
5
|
+
id: S.String,
|
|
6
|
+
sequence: S.Number,
|
|
7
|
+
observedAt: S.String,
|
|
8
|
+
service: S.String,
|
|
9
|
+
scopeId: S.String,
|
|
10
|
+
})
|
|
11
|
+
export type Heartbeat = typeof Heartbeat.Type
|
|
12
|
+
|
|
13
|
+
export const HeartbeatObserved = S.Struct({
|
|
14
|
+
id: S.String,
|
|
15
|
+
sequence: S.Number,
|
|
16
|
+
scopeId: S.String,
|
|
17
|
+
})
|
|
18
|
+
export type HeartbeatObserved = typeof HeartbeatObserved.Type
|
|
19
|
+
|
|
20
|
+
export class HeartbeatUnavailable extends S.TaggedError<HeartbeatUnavailable>()(
|
|
21
|
+
'HeartbeatUnavailable',
|
|
22
|
+
{ reason: S.String },
|
|
23
|
+
) {}
|
|
24
|
+
|
|
25
|
+
export const GetHeartbeat = Rpc.make('GetHeartbeat', {
|
|
26
|
+
payload: { scopeId: S.String },
|
|
27
|
+
success: Heartbeat,
|
|
28
|
+
error: HeartbeatUnavailable,
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
export const PulseRpcs = RpcGroup.make(GetHeartbeat)
|
|
32
|
+
export type PulseRpcs = RpcGroup.Rpcs<typeof PulseRpcs>
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { type Disposable, defineMicroDot } from '@bespokeagentics/microdots-element'
|
|
2
|
+
import { Runtime } from 'foldkit'
|
|
3
|
+
|
|
4
|
+
import { makePulseProgram } from './app.ts'
|
|
5
|
+
import styles from './styles.css?inline'
|
|
6
|
+
import { pulseSurface } from './surface.ts'
|
|
7
|
+
|
|
8
|
+
type Handle = Runtime.EmbedHandle<ReturnType<typeof makePulseProgram>['ports']> &
|
|
9
|
+
Disposable
|
|
10
|
+
|
|
11
|
+
defineMicroDot<Handle, typeof pulseSurface>({
|
|
12
|
+
surface: pulseSurface,
|
|
13
|
+
styles,
|
|
14
|
+
embed: (container, attributes) =>
|
|
15
|
+
Runtime.embed(makePulseProgram(container, attributes)),
|
|
16
|
+
receive: {
|
|
17
|
+
'scope-id': (handle, value) => {
|
|
18
|
+
handle.ports.scopeChanged.send(value)
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
publish: {
|
|
22
|
+
'heartbeat-observed': handle => handle.ports.heartbeatObserved,
|
|
23
|
+
},
|
|
24
|
+
})
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import './element.ts'
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { makeMicroDotSurface } from '@bespokeagentics/microdots-element'
|
|
2
|
+
|
|
3
|
+
import { HeartbeatObserved } from './contract.ts'
|
|
4
|
+
|
|
5
|
+
export const pulseSurface = makeMicroDotSurface({
|
|
6
|
+
tag: 'starter-pulse',
|
|
7
|
+
attributes: {
|
|
8
|
+
'api-url': {
|
|
9
|
+
type: 'string',
|
|
10
|
+
required: true,
|
|
11
|
+
ownership: 'environment',
|
|
12
|
+
description: "Base URL of the pulse readout's own service.",
|
|
13
|
+
},
|
|
14
|
+
'scope-id': {
|
|
15
|
+
type: 'string',
|
|
16
|
+
live: true,
|
|
17
|
+
default: 'general',
|
|
18
|
+
ownership: 'host-input',
|
|
19
|
+
description: 'Selected scope; a live write refetches immediately.',
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
events: {
|
|
23
|
+
'heartbeat-observed': {
|
|
24
|
+
payload: HeartbeatObserved,
|
|
25
|
+
description: 'Emitted after every successful poll.',
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
export const surfaces = [pulseSurface]
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
:where([data-microdot]) {
|
|
2
|
+
--font-body: Inter, ui-sans-serif, system-ui, sans-serif;
|
|
3
|
+
--font-display: Inter, ui-sans-serif, system-ui, sans-serif;
|
|
4
|
+
--font-mono: 'SFMono-Regular', Consolas, monospace;
|
|
5
|
+
--fw-medium: 500;
|
|
6
|
+
--fs-caption: 0.75rem;
|
|
7
|
+
--fs-body-s: 0.82rem;
|
|
8
|
+
--fs-body-m: 0.92rem;
|
|
9
|
+
--fs-button-sm: 0.78rem;
|
|
10
|
+
--fs-display-m: 1.5rem;
|
|
11
|
+
--radius-sm: 0.35rem;
|
|
12
|
+
--radius-md: 0.55rem;
|
|
13
|
+
--radius-lg: 0.8rem;
|
|
14
|
+
--radius-full: 9999px;
|
|
15
|
+
--text-primary: #18202b;
|
|
16
|
+
--text-secondary: #465466;
|
|
17
|
+
--text-muted: #69778a;
|
|
18
|
+
--text-heading: #111827;
|
|
19
|
+
--text-on-brand: #ffffff;
|
|
20
|
+
--surface-default: #ffffff;
|
|
21
|
+
--surface-raised: #f8fafc;
|
|
22
|
+
--border-default: #d5dce6;
|
|
23
|
+
--border-subtle: #e7ebf0;
|
|
24
|
+
--accent-fill: #5264c7;
|
|
25
|
+
--accent-fill-hover: #4355b9;
|
|
26
|
+
--int-hover: rgb(82 100 199 / 0.12);
|
|
27
|
+
--int-active: rgb(82 100 199 / 0.17);
|
|
28
|
+
--status-error-fg: #a12b2b;
|
|
29
|
+
--shadow-card: 0 10px 28px rgb(17 24 39 / 0.08);
|
|
30
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@starter/scope-picker",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"microdot": {
|
|
7
|
+
"tags": ["starter-scope-picker"],
|
|
8
|
+
"port": 3201,
|
|
9
|
+
"bundle": "scope-picker.js"
|
|
10
|
+
},
|
|
11
|
+
"exports": { "./contract": "./src/contract.ts" },
|
|
12
|
+
"dependencies": {
|
|
13
|
+
"@bespokeagentics/microdots-element": "0.1.1",
|
|
14
|
+
"@bespokeagentics/microdots-runtime": "0.1.1",
|
|
15
|
+
"@bespokeagentics/microdots-theme": "0.1.1",
|
|
16
|
+
"@effect/platform-node": "4.0.0-rc.108",
|
|
17
|
+
"effect": "4.0.0-rc.108",
|
|
18
|
+
"foldkit": "^0.144.0"
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { Effect } from 'effect'
|
|
2
|
+
|
|
3
|
+
import { ScopePickerRpcs } from '../src/contract.ts'
|
|
4
|
+
|
|
5
|
+
export const ScopePickerHandlersLive = ScopePickerRpcs.toLayer({
|
|
6
|
+
ListScopes: () =>
|
|
7
|
+
Effect.succeed({
|
|
8
|
+
items: [
|
|
9
|
+
{ id: 'general', label: 'General' },
|
|
10
|
+
{ id: 'operations', label: 'Operations' },
|
|
11
|
+
{ id: 'delivery', label: 'Delivery' },
|
|
12
|
+
],
|
|
13
|
+
observedAt: new Date().toISOString(),
|
|
14
|
+
}),
|
|
15
|
+
})
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { Layer } from 'effect'
|
|
2
|
+
import { RpcSerialization, RpcServer } from 'effect/unstable/rpc'
|
|
3
|
+
import {
|
|
4
|
+
makeRpcService,
|
|
5
|
+
runRpcService,
|
|
6
|
+
} from '@bespokeagentics/microdots-runtime/service'
|
|
7
|
+
|
|
8
|
+
import { ScopePickerRpcs } from '../src/contract.ts'
|
|
9
|
+
import { ScopePickerHandlersLive } from './handlers.ts'
|
|
10
|
+
|
|
11
|
+
const rpcLayer = RpcServer.layerHttp({
|
|
12
|
+
group: ScopePickerRpcs,
|
|
13
|
+
path: '/rpc',
|
|
14
|
+
// Omitting this silently selects WebSocket while the browser POSTs HTTP.
|
|
15
|
+
protocol: 'http',
|
|
16
|
+
}).pipe(
|
|
17
|
+
Layer.provide(ScopePickerHandlersLive),
|
|
18
|
+
Layer.provide(RpcSerialization.layerNdjson),
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
runRpcService(
|
|
22
|
+
makeRpcService({
|
|
23
|
+
rpcLayer,
|
|
24
|
+
port: 3201,
|
|
25
|
+
allowedOrigins: [
|
|
26
|
+
'http://localhost:5190',
|
|
27
|
+
'http://127.0.0.1:5190',
|
|
28
|
+
],
|
|
29
|
+
}),
|
|
30
|
+
)
|