create-foldkit-app 0.30.1 → 0.31.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.
Files changed (59) hide show
  1. package/dist/templates/base/FOLDKIT.md +3 -1
  2. package/dist/templates/examples/auth/src/main.ts +3 -3
  3. package/dist/templates/examples/auth/src/page/loggedIn/model.ts +2 -2
  4. package/dist/templates/examples/auth/src/page/loggedOut/model.ts +2 -2
  5. package/dist/templates/examples/auth/src/route.ts +19 -29
  6. package/dist/templates/examples/auth/src/scene.test.ts +2 -2
  7. package/dist/templates/examples/auth/src/update.ts +3 -4
  8. package/dist/templates/examples/form/src/main.ts +12 -21
  9. package/dist/templates/examples/interrupting-commands/src/scene.test.ts +2 -2
  10. package/dist/templates/examples/interrupting-commands/src/story.test.ts +7 -7
  11. package/dist/templates/examples/job-application/src/main.ts +2 -2
  12. package/dist/templates/examples/job-application/src/model.ts +7 -12
  13. package/dist/templates/examples/job-application/src/scene.test.ts +5 -11
  14. package/dist/templates/examples/job-application/src/story.test.ts +4 -4
  15. package/dist/templates/examples/job-application/src/update.ts +8 -4
  16. package/dist/templates/examples/job-application/src/view/review.ts +92 -94
  17. package/dist/templates/examples/managed-resource-layer/src/main.ts +23 -27
  18. package/dist/templates/examples/managed-resource-layer/src/scene.test.ts +3 -4
  19. package/dist/templates/examples/managed-resource-layer/src/story.test.ts +11 -20
  20. package/dist/templates/examples/map/src/main.fixtures.ts +2 -2
  21. package/dist/templates/examples/map/src/main.ts +23 -21
  22. package/dist/templates/examples/map/src/scene.test.ts +4 -2
  23. package/dist/templates/examples/map/src/story.test.ts +9 -10
  24. package/dist/templates/examples/query-sync/src/main.ts +30 -29
  25. package/dist/templates/examples/query-sync/src/scene.test.ts +8 -15
  26. package/dist/templates/examples/query-sync/src/story.test.ts +21 -8
  27. package/dist/templates/examples/route-transitions/src/main.ts +15 -25
  28. package/dist/templates/examples/route-transitions/src/route.ts +21 -26
  29. package/dist/templates/examples/route-transitions/src/story.test.ts +20 -26
  30. package/dist/templates/examples/routing/src/fileTree.ts +3 -3
  31. package/dist/templates/examples/routing/src/main.ts +2 -12
  32. package/dist/templates/examples/routing/src/page/people.ts +18 -16
  33. package/dist/templates/examples/routing/src/route.ts +18 -32
  34. package/dist/templates/examples/routing/src/scene.test.ts +12 -20
  35. package/dist/templates/examples/routing/src/story.test.ts +9 -6
  36. package/dist/templates/examples/shopping-cart/src/route.ts +11 -19
  37. package/dist/templates/examples/shopping-cart/src/scene.test.ts +10 -8
  38. package/dist/templates/examples/shopping-cart/src/story.test.ts +2 -2
  39. package/dist/templates/examples/ssg/src/route.ts +9 -8
  40. package/dist/templates/examples/state-machine/src/main.ts +68 -63
  41. package/dist/templates/examples/state-machine/src/story.test.ts +8 -7
  42. package/dist/templates/examples/state-machine/src/view.ts +7 -13
  43. package/dist/templates/examples/todo/src/main.ts +11 -17
  44. package/dist/templates/examples/todo/src/scene.test.ts +2 -2
  45. package/dist/templates/examples/todo/src/story.test.ts +11 -12
  46. package/dist/templates/examples/ui-showcase/src/main.ts +64 -85
  47. package/dist/templates/examples/ui-showcase/src/scene.test.ts +14 -30
  48. package/dist/templates/examples/ui-showcase/src/story.test.ts +2 -2
  49. package/dist/templates/examples/ui-showcase/src/ui/message.ts +0 -70
  50. package/dist/templates/examples/view-transitions/src/main.ts +1 -1
  51. package/dist/templates/examples/view-transitions/src/route.ts +9 -12
  52. package/dist/templates/examples/view-transitions/src/scene.test.ts +5 -14
  53. package/dist/templates/examples/websocket-chat/src/main.ts +29 -37
  54. package/dist/templates/examples/websocket-chat/src/scene.test.ts +18 -10
  55. package/dist/templates/examples/websocket-chat/src/story.test.ts +19 -15
  56. package/dist/templates/release.json +13 -13
  57. package/dist/templates/rendering/ssg/src/route.ts +9 -8
  58. package/dist/templates/rendering/ssg/src/scene.test.ts +2 -2
  59. package/package.json +1 -1
@@ -1,4 +1,4 @@
1
- import { Array, Match as M, Option, pipe } from 'effect'
1
+ import { Array, Option, pipe } from 'effect'
2
2
  import { File } from 'foldkit'
3
3
  import type { Html, HtmlBuilder } from 'foldkit/html'
4
4
 
@@ -6,7 +6,7 @@ import { Button } from '@foldkit/ui'
6
6
 
7
7
  import { Step } from '../domain'
8
8
  import { Message } from '../message'
9
- import type { Model } from '../model'
9
+ import { type Model, Submission } from '../model'
10
10
  import { Education, PersonalInfo, Skills, WorkHistory } from '../step'
11
11
  import { employmentRange, pluralize } from './format'
12
12
 
@@ -257,99 +257,97 @@ const submissionSection = (
257
257
  attentionSteps: ReadonlyArray<Step.Step>,
258
258
  h: HtmlBuilder<Message>,
259
259
  ): Html =>
260
- M.value(submission).pipe(
261
- M.tagsExhaustive({
262
- NotSubmitted: () =>
263
- h.div(
264
- [h.Class('pt-4 space-y-2')],
265
- [
266
- ...(shouldShowBlockedNotice
267
- ? [blockedNotice(attentionSteps, h)]
268
- : []),
269
- Button.view(
270
- {
271
- onClick: Message.ClickedSubmit(),
272
- toView: attributes =>
273
- h.button(
274
- [...attributes.button, h.Class(submitButtonClass)],
275
- ['Submit Application'],
276
- ),
277
- },
278
- h,
279
- ),
280
- ],
281
- ),
282
- Submitting: () =>
283
- h.div(
284
- [h.Class('pt-4')],
285
- [
286
- Button.view(
287
- {
288
- toView: attributes =>
289
- h.button(
290
- [
291
- ...attributes.button,
292
- h.Class(
293
- 'w-full rounded-lg bg-indigo-400 px-4 py-3 text-sm font-semibold text-white cursor-wait',
294
- ),
295
- ],
296
- ['Submitting...'],
297
- ),
298
- },
299
- h,
300
- ),
301
- ],
302
- ),
303
- SubmitSuccess: () =>
304
- h.div(
305
- [
306
- h.Role('status'),
307
- h.Class(
308
- 'mt-4 rounded-lg border border-green-200 bg-green-50 p-4 text-center',
309
- ),
310
- ],
311
- [
312
- h.p(
313
- [h.Class('text-lg font-semibold text-green-800')],
314
- ['Application Submitted!'],
315
- ),
316
- h.p(
317
- [h.Class('text-sm text-green-600 mt-1')],
318
- ["Thank you for applying to work on Foldkit. We'll be in touch!"],
319
- ),
320
- ],
321
- ),
322
- SubmitError: ({ error }) =>
323
- h.div(
324
- [h.Class('space-y-3 pt-4')],
325
- [
326
- h.div(
327
- [
328
- h.Role('alert'),
329
- h.Class(
330
- 'rounded-lg border border-red-200 bg-red-50 p-3 text-sm text-red-700',
260
+ Submission.match(submission, {
261
+ NotSubmitted: () =>
262
+ h.div(
263
+ [h.Class('pt-4 space-y-2')],
264
+ [
265
+ ...(shouldShowBlockedNotice
266
+ ? [blockedNotice(attentionSteps, h)]
267
+ : []),
268
+ Button.view(
269
+ {
270
+ onClick: Message.ClickedSubmit(),
271
+ toView: attributes =>
272
+ h.button(
273
+ [...attributes.button, h.Class(submitButtonClass)],
274
+ ['Submit Application'],
331
275
  ),
332
- ],
333
- [error],
334
- ),
335
- ...(shouldShowBlockedNotice
336
- ? [blockedNotice(attentionSteps, h)]
337
- : []),
338
- Button.view(
339
- {
340
- onClick: Message.ClickedSubmit(),
341
- toView: attributes =>
342
- h.button(
343
- [...attributes.button, h.Class(submitButtonClass)],
344
- ['Try Again'],
345
- ),
346
- },
347
- h,
348
- ),
349
- ],
350
- ),
351
- }),
352
- )
276
+ },
277
+ h,
278
+ ),
279
+ ],
280
+ ),
281
+ Submitting: () =>
282
+ h.div(
283
+ [h.Class('pt-4')],
284
+ [
285
+ Button.view(
286
+ {
287
+ toView: attributes =>
288
+ h.button(
289
+ [
290
+ ...attributes.button,
291
+ h.Class(
292
+ 'w-full rounded-lg bg-indigo-400 px-4 py-3 text-sm font-semibold text-white cursor-wait',
293
+ ),
294
+ ],
295
+ ['Submitting...'],
296
+ ),
297
+ },
298
+ h,
299
+ ),
300
+ ],
301
+ ),
302
+ SubmitSuccess: () =>
303
+ h.div(
304
+ [
305
+ h.Role('status'),
306
+ h.Class(
307
+ 'mt-4 rounded-lg border border-green-200 bg-green-50 p-4 text-center',
308
+ ),
309
+ ],
310
+ [
311
+ h.p(
312
+ [h.Class('text-lg font-semibold text-green-800')],
313
+ ['Application Submitted!'],
314
+ ),
315
+ h.p(
316
+ [h.Class('text-sm text-green-600 mt-1')],
317
+ ["Thank you for applying to work on Foldkit. We'll be in touch!"],
318
+ ),
319
+ ],
320
+ ),
321
+ SubmitError: ({ error }) =>
322
+ h.div(
323
+ [h.Class('space-y-3 pt-4')],
324
+ [
325
+ h.div(
326
+ [
327
+ h.Role('alert'),
328
+ h.Class(
329
+ 'rounded-lg border border-red-200 bg-red-50 p-3 text-sm text-red-700',
330
+ ),
331
+ ],
332
+ [error],
333
+ ),
334
+ ...(shouldShowBlockedNotice
335
+ ? [blockedNotice(attentionSteps, h)]
336
+ : []),
337
+ Button.view(
338
+ {
339
+ onClick: Message.ClickedSubmit(),
340
+ toView: attributes =>
341
+ h.button(
342
+ [...attributes.button, h.Class(submitButtonClass)],
343
+ ['Try Again'],
344
+ ),
345
+ },
346
+ h,
347
+ ),
348
+ ],
349
+ ),
350
+ })
353
351
 
354
352
  export const review = (
355
353
  model: Model,
@@ -11,7 +11,7 @@ import {
11
11
  import { Command, ManagedResource, Runtime, type Update } from 'foldkit'
12
12
  import { Document, Html, HtmlBuilder } from 'foldkit/html'
13
13
  import { defineMessageUnion } from 'foldkit/message'
14
- import { ts } from 'foldkit/schema'
14
+ import { defineTaggedUnion } from 'foldkit/schema'
15
15
  import { evo } from 'foldkit/struct'
16
16
 
17
17
  import { BrowserCrypto } from '@effect/platform-browser'
@@ -47,17 +47,12 @@ type EngineService = ManagedResource.ServiceOf<typeof Engine>
47
47
 
48
48
  // MODEL
49
49
 
50
- export const EngineOff = ts('EngineOff')
51
- export const EngineBooting = ts('EngineBooting')
52
- export const EngineReady = ts('EngineReady', { engineId: S.String })
53
- export const EngineFailed = ts('EngineFailed', { reason: S.String })
54
-
55
- const EngineState = S.Union([
56
- EngineOff,
57
- EngineBooting,
58
- EngineReady,
59
- EngineFailed,
60
- ])
50
+ export const EngineState = defineTaggedUnion({
51
+ Off: {},
52
+ Booting: {},
53
+ Ready: { engineId: S.String },
54
+ Failed: { reason: S.String },
55
+ })
61
56
  type EngineState = typeof EngineState.Type
62
57
 
63
58
  export const Model = S.Struct({
@@ -103,21 +98,23 @@ export const Compute = Command.define('Compute', {
103
98
  export const update = (model: Model, message: Message) =>
104
99
  Message.match<Update.Return<Model, Message, EngineService>>(message, {
105
100
  ClickedStartEngine: () => ({
106
- model: evo(model, { engine: () => EngineBooting() }),
101
+ model: evo(model, { engine: () => EngineState.Booting() }),
107
102
  }),
108
103
 
109
104
  ClickedStopEngine: () => ({
110
- model: evo(model, { engine: () => EngineOff() }),
105
+ model: evo(model, { engine: () => EngineState.Off() }),
111
106
  }),
112
107
 
113
108
  StartedEngine: ({ engineId }) => ({
114
- model: evo(model, { engine: () => EngineReady({ engineId }) }),
109
+ model: evo(model, {
110
+ engine: () => EngineState.Ready({ engineId }),
111
+ }),
115
112
  }),
116
113
 
117
114
  StoppedEngine: () => ({ model }),
118
115
 
119
116
  FailedStartEngine: ({ reason }) => ({
120
- model: evo(model, { engine: () => EngineFailed({ reason }) }),
117
+ model: evo(model, { engine: () => EngineState.Failed({ reason }) }),
121
118
  }),
122
119
 
123
120
  ClickedCompute: () => {
@@ -139,7 +136,7 @@ export const update = (model: Model, message: Message) =>
139
136
 
140
137
  export const init: Runtime.ApplicationInit<Model, Message> = () => ({
141
138
  model: {
142
- engine: EngineOff(),
139
+ engine: EngineState.Off(),
143
140
  computeCount: 0,
144
141
  maybeSquareResult: Option.none(),
145
142
  },
@@ -153,8 +150,8 @@ export const managedResources = ManagedResource.make<Model, Message>()(
153
150
  resource: Engine,
154
151
  modelToMaybeRequirements: model =>
155
152
  M.value(model.engine).pipe(
156
- M.tag('EngineBooting', 'EngineReady', () => Option.some(null)),
157
- M.tag('EngineOff', 'EngineFailed', () => Option.none()),
153
+ M.tag('Booting', 'Ready', () => Option.some(null)),
154
+ M.tag('Off', 'Failed', () => Option.none()),
158
155
  M.exhaustive,
159
156
  ),
160
157
  acquire: () =>
@@ -203,19 +200,19 @@ const engineStatusView = (
203
200
  h: HtmlBuilder<Message>,
204
201
  ): Html => {
205
202
  const status = M.value(engine).pipe(
206
- M.tag('EngineOff', () => ({
203
+ M.tag('Off', () => ({
207
204
  colorClassName: 'text-gray-500',
208
205
  text: 'Engine is off.',
209
206
  })),
210
- M.tag('EngineBooting', () => ({
207
+ M.tag('Booting', () => ({
211
208
  colorClassName: 'text-amber-600',
212
209
  text: 'Booting engine...',
213
210
  })),
214
- M.tag('EngineReady', ({ engineId }) => ({
211
+ M.tag('Ready', ({ engineId }) => ({
215
212
  colorClassName: 'text-green-600',
216
213
  text: `Engine ready: ${engineId}`,
217
214
  })),
218
- M.tag('EngineFailed', ({ reason }) => ({
215
+ M.tag('Failed', ({ reason }) => ({
219
216
  colorClassName: 'text-red-600',
220
217
  text: `Engine failed: ${reason}`,
221
218
  })),
@@ -230,12 +227,12 @@ const engineControlsView = (
230
227
  h: HtmlBuilder<Message>,
231
228
  ): Html => {
232
229
  const controls = M.value(engine).pipe(
233
- M.tag('EngineBooting', 'EngineReady', () => ({
230
+ M.tag('Booting', 'Ready', () => ({
234
231
  label: 'Stop engine',
235
232
  message: Message.ClickedStopEngine(),
236
233
  colorClassName: 'bg-red-500 hover:bg-red-600',
237
234
  })),
238
- M.tag('EngineOff', 'EngineFailed', () => ({
235
+ M.tag('Off', 'Failed', () => ({
239
236
  label: 'Start engine',
240
237
  message: Message.ClickedStartEngine(),
241
238
  colorClassName: 'bg-green-500 hover:bg-green-600',
@@ -269,8 +266,7 @@ const squareResultView = (
269
266
  return h.div([h.Class('text-gray-800')], [text])
270
267
  }
271
268
 
272
- const isEngineReady = (engine: EngineState): boolean =>
273
- engine._tag === 'EngineReady'
269
+ const isEngineReady = (engine: EngineState): boolean => engine._tag === 'Ready'
274
270
 
275
271
  export const view = (model: Model, h: HtmlBuilder<Message>): Document => {
276
272
  const isComputeDisabled = !isEngineReady(model.engine)
@@ -13,8 +13,7 @@ import { describe, test } from 'vitest'
13
13
 
14
14
  import {
15
15
  Compute,
16
- EngineOff,
17
- EngineReady,
16
+ EngineState,
18
17
  Message,
19
18
  Model,
20
19
  managedResources,
@@ -23,13 +22,13 @@ import {
23
22
  } from './main'
24
23
 
25
24
  const offModel = Model.make({
26
- engine: EngineOff(),
25
+ engine: EngineState.Off(),
27
26
  computeCount: 0,
28
27
  maybeSquareResult: Option.none(),
29
28
  })
30
29
 
31
30
  const readyModel = Model.make({
32
- engine: EngineReady({ engineId: 'engine-1' }),
31
+ engine: EngineState.Ready({ engineId: 'engine-1' }),
33
32
  computeCount: 2,
34
33
  maybeSquareResult: Option.none(),
35
34
  })
@@ -2,38 +2,29 @@ import { Option } from 'effect'
2
2
  import { Command, given, message, model, story } from 'foldkit/story'
3
3
  import { describe, expect, test } from 'vitest'
4
4
 
5
- import {
6
- Compute,
7
- EngineBooting,
8
- EngineFailed,
9
- EngineOff,
10
- EngineReady,
11
- Message,
12
- type Model,
13
- update,
14
- } from './main'
5
+ import { Compute, EngineState, Message, type Model, update } from './main'
15
6
 
16
7
  const offModel: Model = {
17
- engine: EngineOff(),
8
+ engine: EngineState.Off(),
18
9
  computeCount: 0,
19
10
  maybeSquareResult: Option.none(),
20
11
  }
21
12
 
22
13
  const readyModel: Model = {
23
- engine: EngineReady({ engineId: 'engine-1' }),
14
+ engine: EngineState.Ready({ engineId: 'engine-1' }),
24
15
  computeCount: 2,
25
16
  maybeSquareResult: Option.none(),
26
17
  }
27
18
 
28
19
  describe('update', () => {
29
20
  describe('engine lifecycle', () => {
30
- test('ClickedStartEngine requests the engine by entering EngineBooting', () => {
21
+ test('ClickedStartEngine requests the engine by entering Booting', () => {
31
22
  story(
32
23
  update,
33
24
  given(offModel),
34
25
  message(Message.ClickedStartEngine()),
35
26
  model(model => {
36
- expect(model.engine._tag).toBe('EngineBooting')
27
+ expect(model.engine._tag).toBe('Booting')
37
28
  }),
38
29
  )
39
30
  })
@@ -41,23 +32,23 @@ describe('update', () => {
41
32
  test('StartedEngine marks the engine ready with its id', () => {
42
33
  story(
43
34
  update,
44
- given({ ...offModel, engine: EngineBooting() }),
35
+ given({ ...offModel, engine: EngineState.Booting() }),
45
36
  message(Message.StartedEngine({ engineId: 'engine-7' })),
46
37
  model(model => {
47
38
  expect(model.engine).toStrictEqual(
48
- EngineReady({ engineId: 'engine-7' }),
39
+ EngineState.Ready({ engineId: 'engine-7' }),
49
40
  )
50
41
  }),
51
42
  )
52
43
  })
53
44
 
54
- test('ClickedStopEngine releases the engine by entering EngineOff', () => {
45
+ test('ClickedStopEngine releases the engine by entering Off', () => {
55
46
  story(
56
47
  update,
57
48
  given(readyModel),
58
49
  message(Message.ClickedStopEngine()),
59
50
  model(model => {
60
- expect(model.engine._tag).toBe('EngineOff')
51
+ expect(model.engine._tag).toBe('Off')
61
52
  }),
62
53
  )
63
54
  })
@@ -76,11 +67,11 @@ describe('update', () => {
76
67
  test('FailedStartEngine records the failure reason', () => {
77
68
  story(
78
69
  update,
79
- given({ ...offModel, engine: EngineBooting() }),
70
+ given({ ...offModel, engine: EngineState.Booting() }),
80
71
  message(Message.FailedStartEngine({ reason: 'boot timeout' })),
81
72
  model(model => {
82
73
  expect(model.engine).toStrictEqual(
83
- EngineFailed({ reason: 'boot timeout' }),
74
+ EngineState.Failed({ reason: 'boot timeout' }),
84
75
  )
85
76
  }),
86
77
  )
@@ -1,7 +1,7 @@
1
1
  import { Option } from 'effect'
2
2
 
3
3
  import { Location, featuredLocations } from './locations'
4
- import { GeolocateIdle, type Model } from './main'
4
+ import { GeolocateState, type Model } from './main'
5
5
 
6
6
  export const initialModel: Model = {
7
7
  locations: featuredLocations,
@@ -11,7 +11,7 @@ export const initialModel: Model = {
11
11
  maybeBounds: Option.none(),
12
12
  maybeSelectedLocationId: Option.none(),
13
13
  maybeUserLocation: Option.none(),
14
- geolocateState: GeolocateIdle(),
14
+ geolocateState: GeolocateState.Idle(),
15
15
  }
16
16
 
17
17
  export const mountedModel: Model = {
@@ -11,12 +11,12 @@ import {
11
11
  Stream,
12
12
  String,
13
13
  } from 'effect'
14
- import { Command, Mount, Runtime, Subscription, type Update } from 'foldkit'
14
+ import { Command, Mount, Runtime, Subscription, Update } from 'foldkit'
15
15
  import * as Dom from 'foldkit/dom'
16
16
  import type { Document, Html } from 'foldkit/html'
17
17
  import { HtmlBuilder } from 'foldkit/html'
18
18
  import { defineMessageUnion } from 'foldkit/message'
19
- import { ts } from 'foldkit/schema'
19
+ import { defineTaggedUnion } from 'foldkit/schema'
20
20
  import { evo } from 'foldkit/struct'
21
21
  import type { Map as MapInstance } from 'maplibre-gl'
22
22
 
@@ -45,16 +45,12 @@ type Bounds = typeof Bounds.Type
45
45
  const LngLat = S.Struct({ lng: S.Number, lat: S.Number })
46
46
  type LngLat = typeof LngLat.Type
47
47
 
48
- export const GeolocateIdle = ts('GeolocateIdle')
49
- export const GeolocateLocating = ts('GeolocateLocating')
50
- export const GeolocateFailed = ts('GeolocateFailed', { reason: S.String })
51
-
52
- const GeolocateState = S.Union([
53
- GeolocateIdle,
54
- GeolocateLocating,
55
- GeolocateFailed,
56
- ])
57
- type GeolocateState = typeof GeolocateState.Type
48
+ export const GeolocateState = defineTaggedUnion({
49
+ Idle: {},
50
+ Locating: {},
51
+ Failed: { reason: S.String },
52
+ })
53
+ export type GeolocateState = typeof GeolocateState.Type
58
54
 
59
55
  export const Model = S.Struct({
60
56
  locations: S.Array(Location),
@@ -254,19 +250,23 @@ export const update = (model: Model, message: Message) =>
254
250
  }),
255
251
 
256
252
  ClickedFindMe: () => ({
257
- model: evo(model, { geolocateState: () => GeolocateLocating() }),
253
+ model: evo(model, {
254
+ geolocateState: () => GeolocateState.Locating(),
255
+ }),
258
256
  commands: [LockBodyScroll(), Geolocate()],
259
257
  }),
260
258
 
261
259
  DismissedGeolocate: () => ({
262
- model: evo(model, { geolocateState: () => GeolocateIdle() }),
260
+ model: evo(model, {
261
+ geolocateState: () => GeolocateState.Idle(),
262
+ }),
263
263
  commands: [UnlockBodyScroll()],
264
264
  }),
265
265
 
266
266
  SucceededGeolocate: ({ lng, lat }) => ({
267
267
  model: evo(model, {
268
268
  maybeUserLocation: () => Option.some({ lng, lat }),
269
- geolocateState: () => GeolocateIdle(),
269
+ geolocateState: () => GeolocateState.Idle(),
270
270
  }),
271
271
  commands: [
272
272
  UnlockBodyScroll(),
@@ -280,7 +280,9 @@ export const update = (model: Model, message: Message) =>
280
280
  }),
281
281
 
282
282
  FailedGeolocate: ({ reason }) => ({
283
- model: evo(model, { geolocateState: () => GeolocateFailed({ reason }) }),
283
+ model: evo(model, {
284
+ geolocateState: () => GeolocateState.Failed({ reason }),
285
+ }),
284
286
  }),
285
287
 
286
288
  SucceededFlyTo: () => ({ model }),
@@ -301,7 +303,7 @@ export const init: Runtime.ApplicationInit<Model, Message> = () => ({
301
303
  maybeBounds: Option.none(),
302
304
  maybeSelectedLocationId: Option.none(),
303
305
  maybeUserLocation: Option.none(),
304
- geolocateState: GeolocateIdle(),
306
+ geolocateState: GeolocateState.Idle(),
305
307
  },
306
308
  commands: [FocusSearchInput()],
307
309
  })
@@ -584,7 +586,7 @@ const locationListItemView =
584
586
  }
585
587
 
586
588
  const footerView = (model: Model, h: HtmlBuilder<Message>): Html => {
587
- const isLocating = model.geolocateState._tag === 'GeolocateLocating'
589
+ const isLocating = model.geolocateState._tag === 'Locating'
588
590
  return h.div(
589
591
  [h.Class('border-t border-slate-200 px-5 py-3 space-y-2')],
590
592
  [
@@ -680,10 +682,10 @@ const geolocateOverlayView = (
680
682
  ): Html =>
681
683
  M.value(state).pipe(
682
684
  M.tagsExhaustive({
683
- GeolocateIdle: () => h.empty,
684
- GeolocateLocating: () =>
685
+ Idle: () => h.empty,
686
+ Locating: () =>
685
687
  geolocateOverlayShellView(geolocateLocatingContentView(h), h),
686
- GeolocateFailed: ({ reason }) =>
688
+ Failed: ({ reason }) =>
687
689
  geolocateOverlayShellView(geolocateFailedContentView(reason, h), h),
688
690
  }),
689
691
  )
@@ -16,7 +16,7 @@ import { describe, test } from 'vitest'
16
16
  import {
17
17
  FlyTo,
18
18
  Geolocate,
19
- GeolocateFailed,
19
+ GeolocateState,
20
20
  LockBodyScroll,
21
21
  Message,
22
22
  MountMap,
@@ -93,7 +93,9 @@ describe('view', () => {
93
93
  { update, view },
94
94
  given({
95
95
  ...mountedModel,
96
- geolocateState: GeolocateFailed({ reason: 'Permission denied' }),
96
+ geolocateState: GeolocateState.Failed({
97
+ reason: 'Permission denied',
98
+ }),
97
99
  }),
98
100
  acknowledgeMapMount,
99
101
  expect(role('button', { name: 'Dismiss' })).toExist(),
@@ -5,8 +5,7 @@ import { expect, test } from 'vitest'
5
5
  import {
6
6
  FlyTo,
7
7
  Geolocate,
8
- GeolocateFailed,
9
- GeolocateLocating,
8
+ GeolocateState,
10
9
  LockBodyScroll,
11
10
  Message,
12
11
  UnlockBodyScroll,
@@ -126,7 +125,7 @@ test('clicking find-me transitions to the locating state and emits Geolocate', (
126
125
  given(initialModel),
127
126
  message(Message.ClickedFindMe()),
128
127
  model(model => {
129
- expect(model.geolocateState._tag).toBe('GeolocateLocating')
128
+ expect(model.geolocateState._tag).toBe('Locating')
130
129
  }),
131
130
  Command.expectHas(LockBodyScroll, Geolocate),
132
131
  Command.resolve(LockBodyScroll, Message.CompletedLockBodyScroll()),
@@ -142,11 +141,11 @@ test('a successful geolocation result clears the locating state and flies the ma
142
141
  update,
143
142
  given({
144
143
  ...mountedModel,
145
- geolocateState: GeolocateLocating(),
144
+ geolocateState: GeolocateState.Locating(),
146
145
  }),
147
146
  message(Message.SucceededGeolocate({ lng: 2.35, lat: 48.85 })),
148
147
  model(model => {
149
- expect(model.geolocateState._tag).toBe('GeolocateIdle')
148
+ expect(model.geolocateState._tag).toBe('Idle')
150
149
  expect(model.maybeUserLocation).toStrictEqual(
151
150
  Option.some({ lng: 2.35, lat: 48.85 }),
152
151
  )
@@ -162,12 +161,12 @@ test('a failed geolocation result surfaces the reason in the geolocate state', (
162
161
  update,
163
162
  given({
164
163
  ...initialModel,
165
- geolocateState: GeolocateLocating(),
164
+ geolocateState: GeolocateState.Locating(),
166
165
  }),
167
166
  message(Message.FailedGeolocate({ reason: 'Permission denied' })),
168
167
  model(model => {
169
- expect(model.geolocateState._tag).toBe('GeolocateFailed')
170
- if (model.geolocateState._tag === 'GeolocateFailed') {
168
+ expect(model.geolocateState._tag).toBe('Failed')
169
+ if (model.geolocateState._tag === 'Failed') {
171
170
  expect(model.geolocateState.reason).toBe('Permission denied')
172
171
  }
173
172
  }),
@@ -179,11 +178,11 @@ test('dismissing the geolocate overlay returns to idle', () => {
179
178
  update,
180
179
  given({
181
180
  ...initialModel,
182
- geolocateState: GeolocateFailed({ reason: 'Timed out' }),
181
+ geolocateState: GeolocateState.Failed({ reason: 'Timed out' }),
183
182
  }),
184
183
  message(Message.DismissedGeolocate()),
185
184
  model(model => {
186
- expect(model.geolocateState._tag).toBe('GeolocateIdle')
185
+ expect(model.geolocateState._tag).toBe('Idle')
187
186
  }),
188
187
  Command.resolve(UnlockBodyScroll, Message.CompletedUnlockBodyScroll()),
189
188
  )