From 1aab2b977f318ba9a237266b29f32bec64c85c3c Mon Sep 17 00:00:00 2001 From: tiye Date: Mon, 9 Nov 2026 21:45:22 +0800 Subject: [PATCH] =?UTF-8?q?=E5=88=86=E7=A6=BB=20Data=20Former=20=E5=BA=94?= =?UTF-8?q?=E7=94=A8=E6=B4=BE=E5=8F=91=E4=B8=8E=20Reel=20=E6=8E=A7?= =?UTF-8?q?=E5=88=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- calcit.cirru | 39 ++++++++++++------------------ deps.cirru | 2 +- scripts/former-regression.test.mjs | 12 +++++---- 3 files changed, 23 insertions(+), 30 deletions(-) diff --git a/calcit.cirru b/calcit.cirru index 1a9dd19..9d7ffca 100644 --- a/calcit.cirru +++ b/calcit.cirru @@ -6,7 +6,7 @@ {} (:description |) (:init-fn 'app.main/main!) (:mode :js) (:reload-fn 'app.main/reload!) (:target :browser) :feature-policy $ {} :modules $ [] |respo.calcit/ |respo-ui.calcit/ |reel.calcit/ |js-ffi/ - :type-slots $ {} $ :dispatch-op 'app.schema/RenderEvent + :type-slots $ {} $ :dispatch-op 'app.schema/Op :files $ {} 'app.comp.button $ %{} 'FileEntry :defs $ {} $ 'comp-live-button @@ -62,7 +62,7 @@ :examples $ [] :schema $ quote UnionDef 'comp-container $ %{} 'CodeEntry (:doc |) - :code $ quote $ defcomp comp-container (reel) + :code $ quote $ defcomp comp-container (reel on-control) let store $ :store reel states $ :states store @@ -106,10 +106,13 @@ -> Unit d! $ Op :data $ input-text e if dev? - Option :some $ reel.comp.reel/comp-typed-reel (>> states :reel) reel $ {} + Option :some $ reel.comp.reel/comp-typed-reel (>> states :reel) reel ({}) on-control Option :none :examples $ [] - :schema $ quote $ Fn (reel.typed/State app.schema/Op app.schema/Store) (-> respo.schema/Component) (:features :js-ffi) + :schema $ quote $ Fn (reel.typed/State app.schema/Op app.schema/Store) + Fn reel.typed/Control $ -> Unit + -> respo.schema/Component + :features :js-ffi 'input-text $ %{} 'CodeEntry (:doc |) :code $ quote $ defn input-text (event) let @@ -193,17 +196,6 @@ , &unit :examples $ [] :schema $ quote $ Fn app.schema/ResolvedOp (-> Unit) (:features :js-ffi) - 'dispatch-render! $ %{} 'CodeEntry (:doc |) - :code $ quote $ defn dispatch-render! (op) - match (reel.typed/decode-control op) - (:some control) (dispatch! control) - (:none) - if - = (enum-definition op) (Option :some schema/Op) - dispatch! $ decode-map-as op 'app.schema/Op - raise |unknown-former-operation - :examples $ [] - :schema $ quote $ Fn AnyEnum (-> Unit) (:features :js-ffi) 'main! $ %{} 'CodeEntry (:doc |) :code $ quote $ defn main! () println "|Running mode:" $ if config/dev? |dev |release @@ -253,8 +245,12 @@ 'render-app! $ %{} 'CodeEntry (:doc |) :code $ quote $ defn render-app! () render-with! (mount-target) - fn () $ comp-container @*reel - , dispatch-render! + fn () $ comp-container @*reel $ fn (control) + hint-fn $ Fn reel.typed/Control $ -> Unit + dispatch! control + fn (op) + hint-fn $ Fn app.schema/Op $ -> Unit + dispatch! op :examples $ [] :schema $ quote $ Fn (-> Unit) (:features :js-ffi) :ns $ %{} 'NsEntry (:doc |) @@ -280,18 +276,13 @@ :hydrate-storage app.schema/Store :examples $ [] :schema $ quote EnumDef - 'RenderEvent $ %{} 'CodeEntry - :doc "|Respo entry slot for nominal UI operations and legacy anonymous Reel events." - :code $ quote $ def RenderEvent (:: :unhandled) - :examples $ [] - :schema $ quote AnyEnum 'ResolvedOp $ %{} 'CodeEntry (:doc |) :code $ quote $ defunion ResolvedOp app.schema/Op reel.typed/Control :examples $ [] :schema $ quote UnionDef 'Store $ %{} 'CodeEntry (:doc |) :code $ quote $ defstruct Store - :states $ Map Dynamic Dynamic + :states $ Map respo.schema/CoordKey Dynamic :content String :data Dynamic :examples $ [] @@ -350,7 +341,7 @@ (:states cursor s) assoc store :states $ decode-map-as respo.cursor/update-state-tree (:states store) cursor s - Map Dynamic Dynamic + Map respo.schema/CoordKey Dynamic (:content data) (assoc store :content data) (:data data) (assoc store :data data) (:hydrate-storage data) data diff --git a/deps.cirru b/deps.cirru index 535da64..34a2aab 100644 --- a/deps.cirru +++ b/deps.cirru @@ -1,7 +1,7 @@ {} (:calcit-version |0.38.0) :version |0.1.2 - :dependencies $ {} (|Respo/reel.calcit |0.10.0) + :dependencies $ {} (|Respo/reel.calcit |0.11.0) |Respo/respo-ui.calcit |0.15.2 |Respo/respo.calcit |0.19.5 |calcit-lang/js-ffi |0.2.13 diff --git a/scripts/former-regression.test.mjs b/scripts/former-regression.test.mjs index 8be3d41..0b11787 100644 --- a/scripts/former-regression.test.mjs +++ b/scripts/former-regression.test.mjs @@ -15,6 +15,7 @@ const t = c.init_tags(['event', 'children', 'click', 'input', 'some', 'content', const map = c._$n__$M_; const inputEvent = (target) => event__GT_edn({ type: 'input', target }); const clickEvent = () => event__GT_edn({ type: 'click' }); +const rejectControl = () => assert.fail('Application events must use Respo dispatch'); const field = (v, k) => c.option_$o_unwrap(c.get(v, k)); const nth = (v, i) => c.option_$o_unwrap(c.nth(v, i)); const en = c._$n_enum_$o_nth; @@ -54,7 +55,7 @@ test('live button dispatch and timer are single typed state operations', () => w })); test('Run converts Cirru EDN to Lisp and keeps nested button state isolated', () => withTimer(getTimer => { let next = c.assoc(store, t.content, '[] 1 2'); - handlers(comp_container(new_reel(next)), t.click, 'Run')[0](clickEvent(), (...args) => { + handlers(comp_container(new_reel(next), rejectControl), t.click, 'Run')[0](clickEvent(), (...args) => { assert.equal(args.length, 1); next = updater(next, args[0], 'run', 1); }); @@ -63,7 +64,7 @@ test('Run converts Cirru EDN to Lisp and keeps nested button state isolated', () assert.equal(field(next, t.data), '(1 2)'); })); test('both textareas dispatch a single typed operation to their own field', () => { - const inputs = handlers(comp_container(new_reel(store)), t.input); + const inputs = handlers(comp_container(new_reel(store), rejectControl), t.input); assert.equal(inputs.length, 2); for (const [i, key] of [t.content, t.data].entries()) { let next; @@ -73,15 +74,16 @@ test('both textareas dispatch a single typed operation to their own field', () = }); test('missing input value is explicitly unwrapped to empty string', () => { let next; - handlers(comp_container(new_reel(store)), t.input)[0](inputEvent({}), (...args) => { next = updater(store, args[0], 'input', 1); }); + handlers(comp_container(new_reel(store), rejectControl), t.input)[0](inputEvent({}), (...args) => { next = updater(store, args[0], 'input', 1); }); assert.equal(field(next, t.content), ''); }); test('typed hydrate-storage operation preserves the exact Store', () => { - const saved = c.assoc(store, t.content, 'saved'); + const states = map(t.run, map(t.data, map(t['darken?'], true)), 'panel', map(), 1, map()); + const saved = c.assoc(c.assoc(store, t.content, 'saved'), t.states, states); assert.equal(restore_store(saved), saved); const wire = c.parse_cirru_edn(c.format_cirru_edn(saved)); assert.ok(c._$e_(restore_store(wire), saved)); - assert.ok(c._$e_(restore_store(map(t.states, map(), t.content, 'saved', t.data, field(saved, t.data))), saved)); + assert.ok(c._$e_(restore_store(map(t.states, states, t.content, 'saved', t.data, field(saved, t.data))), saved)); assert.throws(() => restore_store(map(t.states, map(), t.content, 42, t.data, 'saved'))); assert.ok(c._$e_(updater(store, c._PCT__$o__$o_(Op, c.newTag('hydrate-storage'), saved), 'hydrate', 1), saved)); });