From c336059065e7af76e950dc87a84484b25889c5b3 Mon Sep 17 00:00:00 2001 From: tiye Date: Fri, 16 Oct 2026 08:43:58 +0800 Subject: [PATCH 1/4] fix: migrate Tabletwo Map lookups and preserve moved paragraph cursor --- README.md | 9 ++ calcit.cirru | 402 +++++++++++++++++++++++++++++++++++++-------------- deps.cirru | 2 +- package.json | 2 +- yarn.lock | 10 +- 5 files changed, 307 insertions(+), 118 deletions(-) diff --git a/README.md b/README.md index 2734335..8b625c0 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,13 @@ `package.json` 中的 `@calcit/procs` 必须与它精确配对。 模块使用 `deps.cirru` 中的正式 tag,npm 新包安装保留一小时隔离。 +当前 CLI / `@calcit/procs` 精确配对正式 0.30.0。本轮为 0.30.1 迁移准备, +将应用和原测试的底层 Map 读取改为公开 `get` / Option 接口;缺失值仍还原为 +原来的 nil,保留现有 decoder、false、0 和默认值,不新增统一包装或强转。 +段落排序使用类型化 `MapEntry` 保留 String key。拖放缺少 target/base 时明确报错; +同时修复原 `update` 把嵌套 session 路径当单键、移动后未更新编辑光标的问题, +改用 `update-in` 并处理 Option 输入,Number session key 不变。 + ```sh caps --strict --ci yarn install --immutable @@ -86,6 +93,8 @@ calcit calcit.cirru --entry server ## 限制 - 完整交互及迁移验收尚未完成,局部测试不代表整个应用已可发布。 +- 0.30.1 尚未正式发布;alpha.4 的只读探测仍被 WS-EDN、Respo、Recollect + 等已锁定依赖的旧底层接口阻塞。本轮不改用 alpha、不宣称整图已兼容 0.30.1。 - 删除 Confirm 与 Text 弹窗完整内容仍需独立验收。 - 框架 state、投影和宿主的开放边界仍存在,不宣称全应用已静态化。 - Highlight.js 保留原 legacy 调用及弃用提示,依赖中仍有旧 require 写法告警。 diff --git a/calcit.cirru b/calcit.cirru index c97b39b..2df8a47 100644 --- a/calcit.cirru +++ b/calcit.cirru @@ -113,9 +113,11 @@ :code $ quote $ defn on-server-data (data) let data-map $ decode-map-as data $ :: 'Map 'Tag 'Dynamic - case-default (&map:get data-map :kind) (js/console.warn "|unknown server data kind:" data) + case-default + .unwrap-or (get data-map :kind) nil + js/console.warn "|unknown server data kind:" data :patch $ let - changes $ &map:get data-map :data + changes $ .unwrap-or (get data-map :data) nil when config/dev? $ js/console.log |Changes $ to-js-data changes let base $ match @*store @@ -156,7 +158,8 @@ 'render-app! $ %{} 'CodeEntry (:doc |) :code $ quote $ defn render-app! () render! mount-target - comp-container (&map:get @*states :states) + comp-container + .unwrap-or (get @*states :states) nil match @*store (:some online-store) online-store (:none) nil @@ -214,7 +217,9 @@ let article-map $ decode-map-as article $ :: 'Map 'Tag 'Dynamic article-title $ read-article-title article - article-id $ decode-map-as (&map:get article-map :id) 'String + article-id $ decode-map-as + .unwrap-or (get article-map :id) nil + , 'String focuses-map $ decode-map-as focuses $ :: 'Map 'String :: 'List $ :: 'Map 'Tag 'Dynamic edit-plugin $ use-prompt (>> states :edit) @@ -261,7 +266,8 @@ .map $ fn (info) let info-map $ decode-map-as info $ :: 'Map 'Tag 'Dynamic - [] (&map:get info-map :id) + [] + .unwrap-or (get info-map :id) nil div {} $ :style $ {} (:margin-right 8) match (read-focus-name info-map) @@ -279,9 +285,13 @@ let router-map $ decode-map-as router-data $ :: 'Map 'Tag 'Dynamic articles $ decode-map-as - or (&map:get router-map :articles) ({}) + or + .unwrap-or (get router-map :articles) nil + {} :: 'Map 'String $ :: 'Map 'Tag 'Dynamic - focuses $ or (&map:get router-map :focuses) ({}) + focuses $ or + .unwrap-or (get router-map :focuses) nil + {} create-plugin $ use-prompt (>> states :create) {} (:text "|Title of article:") (:initial |) div @@ -303,7 +313,9 @@ let article-map $ decode-map-as article $ :: 'Map 'Tag 'Dynamic [] k $ comp-article - >> states $ decode-map-as (&map:get article-map :id) 'String + >> states $ decode-map-as + .unwrap-or (get article-map :id) nil + , 'String , article focuses div ({}) button @@ -324,7 +336,9 @@ :code $ quote $ defn read-article-title (value) let value-map $ decode-map-as value $ :: 'Map 'Tag 'Dynamic - decode-map-as (&map:get value-map :title) 'String + decode-map-as + .unwrap-or (get value-map :title) nil + , 'String :examples $ [] :schema $ :: 'Fn $ {} (:return 'String) :args $ [] 'Dynamic @@ -359,7 +373,7 @@ :code $ quote $ defn read-focus-name (info) let info-map $ decode-map-as info $ :: 'Map 'Tag 'Dynamic - raw-name $ &map:get info-map :name + raw-name $ .unwrap-or (get info-map :name) nil if (nil? raw-name) (Option :none) Option :some $ decode-map-as raw-name 'String :examples $ [] @@ -399,17 +413,23 @@ states-map $ unsafe-coerce states 'Map store-map $ decode-map-as store $ :: 'Map 'Tag 'Dynamic navigation-view $ read-navigation-view store-map - session $ decode-map-as (&map:get store-map :session) (:: 'Map 'Tag 'Dynamic) - paragraph-id $ &map:get session :paragraph-id + session $ decode-map-as + .unwrap-or (get store-map :session) nil + :: 'Map 'Tag 'Dynamic + paragraph-id $ .unwrap-or (get session :paragraph-id) nil router $ if (:logged-in? navigation-view) - decode-map-as (&map:get store-map :router) (:: 'Map 'Tag 'Dynamic) + decode-map-as + .unwrap-or (get store-map :router) nil + :: 'Map 'Tag 'Dynamic {} - router-data $ &map:get router :data - raw-color $ &map:get store-map :color + router-data $ .unwrap-or (get router :data) nil + raw-color $ .unwrap-or (get store-map :color) nil status-color $ if (nil? raw-color) (Option :none) Option :some $ decode-map-as raw-color 'String article-data $ if - = (&map:get router :name) :article + = + .unwrap-or (get router :name) nil + , :article decode-map-as router-data $ :: 'Map 'Tag 'Dynamic {} div @@ -423,11 +443,13 @@ unsafe-coerce ui/row $ :: 'Map 'Tag 'Dynamic comp-navigation (:logged-in? navigation-view) (:count navigation-view) if (:logged-in? navigation-view) - case-default (&map:get router :name) + case-default + .unwrap-or (get router :name) nil div {} $ :style ui/flex <> $ str router - :profile $ comp-profile (&map:get store-map :user) + :profile $ comp-profile + .unwrap-or (get store-map :user) nil decode-map-as router-data $ :: 'Map 'Number 'Dynamic :home $ comp-articles (>> states :articles) decode-map-as router-data $ :: 'Map 'Tag 'Dynamic @@ -435,11 +457,15 @@ {} $ :style $ merge unsafe-coerce ui/row $ :: 'Map 'Tag 'Dynamic unsafe-coerce ui/flex $ :: 'Map 'Tag 'Dynamic - comp-previewer (>> states :previewer) (&map:get article-data :article) (&map:get article-data :focuses) (&map:get article-data :members) paragraph-id + comp-previewer (>> states :previewer) + .unwrap-or (get article-data :article) nil + .unwrap-or (get article-data :focuses) nil + .unwrap-or (get article-data :members) nil + , paragraph-id comp-login $ >> states :login let visible? $ and (:logged-in? navigation-view) (non-nil? paragraph-id) - = :article $ &map:get router :name + = :article $ .unwrap-or (get router :name) nil comp-editor-panel (>> states :editor) paragraph-id option:unwrap-or get-in article-data $ [] :article :paragraphs paragraph-id @@ -490,9 +516,11 @@ :code $ quote $ defn read-navigation-view (store) let store-map $ decode-map-as store $ :: 'Map 'Tag 'Dynamic - raw-count $ &map:get store-map :count + raw-count $ .unwrap-or (get store-map :count) nil NavigationView :logged-in? - decode-map-as (&map:get store-map :logged-in?) 'Bool + decode-map-as + .unwrap-or (get store-map :logged-in?) nil + , 'Bool , :count $ if (nil? raw-count) (Option :none) Option :some $ decode-map-as raw-count 'Number :examples $ [] @@ -517,7 +545,9 @@ view $ read-navigation-view store assert= false $ :logged-in? view assert= (Option :some 2) (:count view) - assert= |preserved $ &map:get store :payload + assert= |preserved $ match (get store :payload) + (:some lookup-value) lookup-value + (:none) nil :tags $ #{} :navigation-view :unit %{} 'TestEntry (:name |rejects-untyped-navigation-values) :code $ quote $ do @@ -557,9 +587,11 @@ 'read-reel-length $ %{} 'CodeEntry (:doc |) :code $ quote $ defn read-reel-length (store) decode-map-as - &map:get - decode-map-as store $ :: 'Map 'Tag 'Dynamic - , :reel-length + .unwrap-or + get + decode-map-as store $ :: 'Map 'Tag 'Dynamic + , :reel-length + , nil , 'Number :examples $ [] :schema $ :: 'Fn $ {} (:return 'Number) @@ -630,9 +662,10 @@ let states-map $ decode-map-as states 'Map paragraph-map $ decode-map-as paragraph $ :: 'Map 'Tag 'Dynamic - cursor $ &map:get states-map :cursor + cursor $ .unwrap-or (get states-map :cursor) nil state $ decode-map-as - or (&map:get states-map :data) + or + .unwrap-or (get states-map :data) nil {} (:text |) (:time 0) :: 'Map 'Tag 'Dynamic remove-plugin $ use-confirm (>> states :remove) @@ -713,11 +746,19 @@ let state-map $ decode-map-as state $ :: 'Map 'Tag 'Dynamic paragraph-map $ decode-map-as paragraph $ :: 'Map 'Tag 'Dynamic - state-time $ decode-map-as (&map:get state-map :time) 'Number - paragraph-time $ decode-map-as (&map:get paragraph-map :time) 'Number + state-time $ decode-map-as + .unwrap-or (get state-map :time) nil + , 'Number + paragraph-time $ decode-map-as + .unwrap-or (get paragraph-map :time) nil + , 'Number if (> state-time paragraph-time) - decode-map-as (&map:get state-map :text) 'String - decode-map-as (&map:get paragraph-map :content) 'String + decode-map-as + .unwrap-or (get state-map :text) nil + , 'String + decode-map-as + .unwrap-or (get paragraph-map :content) nil + , 'String :examples $ [] :schema $ :: 'Fn $ {} (:return 'String) :args $ [] 'Dynamic 'Dynamic @@ -880,9 +921,13 @@ :code $ quote $ defcomp comp-login (states) let states-map $ decode-map-as states 'Map - cursor $ decode-map-as (&map:get states-map :cursor) (:: 'List 'Dynamic) + cursor $ decode-map-as + .unwrap-or (get states-map :cursor) nil + :: 'List 'Dynamic state $ decode-map-as - or (&map:get states-map :data) initial-state + or + .unwrap-or (get states-map :data) nil + , initial-state :: 'Map 'Tag 'Dynamic fields $ read-login-fields state div @@ -933,7 +978,9 @@ :: 'Fn $ {} (:rest 'Dynamic) (:return 'Unit) :args $ [] 'Dynamic dispatch! $ :: :states cursor $ assoc state field - decode-map-as (&map:get event :value) 'String + decode-map-as + .unwrap-or (get event :value) nil + , 'String , &unit :examples $ [] :schema $ :: 'Fn $ {} (:return 'respo.schema/EventHandler) @@ -959,7 +1006,9 @@ reset! calls $ inc $ deref calls , &unit assert= 1 $ deref calls - assert= |old $ &map:get state :username + assert= |old $ match (get state :username) + (:some lookup-value) lookup-value + (:none) nil :tags $ #{} :login-fields :unit %{} 'TestEntry (:name |rejects-non-text-before-dispatch) :code $ quote $ let @@ -1007,8 +1056,12 @@ let state-map $ decode-map-as state $ :: 'Map 'Tag 'Dynamic LoginFields :username - decode-map-as (&map:get state-map :username) 'String - , :password $ decode-map-as (&map:get state-map :password) 'String + decode-map-as + .unwrap-or (get state-map :username) nil + , 'String + , :password $ decode-map-as + .unwrap-or (get state-map :password) nil + , 'String :examples $ [] :schema $ :: 'Fn $ {} (:return 'app.comp.login/LoginFields) :args $ [] 'Dynamic @@ -1022,7 +1075,9 @@ assert= | $ :password empty-fields assert= "|用户🌱" $ :username filled assert= "|密钥" $ :password filled - assert= false $ &map:get source :extra + assert= false $ match (get source :extra) + (:some lookup-value) lookup-value + (:none) nil :tags $ #{} :login-fields :unit %{} 'TestEntry (:name |rejects-invalid-and-missing-fields) :code $ quote $ do @@ -1146,7 +1201,8 @@ :code $ quote $ defn article-text (article) let title $ read-article-title article - paragraphs $ decode-map-as (&map:get article :paragraphs) + paragraphs $ decode-map-as + .unwrap-or (get article :paragraphs) nil :: 'Map 'String $ :: 'Map 'Tag 'Dynamic entries $ map-entries paragraphs contents $ -> entries @@ -1204,7 +1260,8 @@ -> focus-records $ .map $ fn (info) let info-map info - [] (&map:get info-map :sid) + [] + .unwrap-or (get info-map :sid) nil div {} $ :style $ {} (:padding "|0 8px") (:border-radius |16px) (:margin-right 8) :border $ str "|1px solid " $ hsl 0 0 90 @@ -1222,8 +1279,9 @@ states-map $ decode-map-as states 'Map paragraph-map $ decode-map-as paragraph $ :: 'Map 'Tag 'Dynamic paragraph-text $ read-paragraph-content paragraph - cursor $ &map:get states-map :cursor - state $ or (&map:get states-map :data) + cursor $ .unwrap-or (get states-map :cursor) nil + state $ or + .unwrap-or (get states-map :data) nil {} (:text |) (:time 0) div ParagraphDropProps :style @@ -1348,7 +1406,8 @@ focuses-map $ decode-map-as focuses $ :: 'Map 'String :: 'List $ :: 'Map 'Tag 'Dynamic members-map $ decode-map-as members $ :: 'Map 'Number 'Dynamic - paragraphs-map $ decode-map-as (&map:get article-map :paragraphs) + paragraphs-map $ decode-map-as + .unwrap-or (get article-map :paragraphs) nil :: 'Map 'String $ :: 'Map 'Tag 'Dynamic div {} $ :style $ merge ui/flex ui/column @@ -1389,9 +1448,17 @@ list-> {} $ :style $ merge ui/flex ui/column {} $ :border $ str "|1px solid " (hsl 0 0 94) - -> paragraphs-map (.to-list) (.sort-by first) - .map-pair $ fn (k paragraph) - [] k $ comp-paragraph (>> states k) k paragraph (&map:get focuses-map k) (= k sort-id) + -> paragraphs-map (map-entries) + .sort-by $ fn (entry) (:key entry) + .map $ fn (entry) + let + k $ :key entry + paragraph $ :value entry + [] k $ comp-paragraph (>> states k) k paragraph + match (get focuses-map k) + (:some lookup-value) lookup-value + (:none) nil + = k sort-id =< nil 300 :examples $ [] :schema $ :: 'Fn $ {} (:return 'respo.schema/Component) @@ -1449,7 +1516,9 @@ :code $ quote $ defn read-paragraph-content (value) let value-map $ decode-map-as value $ :: 'Map 'Tag 'Dynamic - decode-map-as (&map:get value-map :content) 'String + decode-map-as + .unwrap-or (get value-map :content) nil + , 'String :examples $ [] :schema $ :: 'Fn $ {} (:return 'String) :args $ [] 'Dynamic @@ -1517,7 +1586,7 @@ {} $ :padding 16 div {} $ :style $ {} (:font-family ui/font-fancy) (:font-size 32) (:font-weight 100) - <> $ str "|Hello! " $ &map:get user-map :name + <> $ str "|Hello! " $ .unwrap-or (get user-map :name) nil =< nil 16 div {} $ :style ui/row @@ -1876,8 +1945,14 @@ let now $ extract-time $ get-time! join-path calcit-dirname |backups - str $ &map:get now :month - str (&map:get now :day) |-snapshot.cirru + str $ match (get now :month) + (:some lookup-value) lookup-value + (:none) nil + str + match (get now :day) + (:some lookup-value) lookup-value + (:none) nil + , |-snapshot.cirru :examples $ [] :schema $ :: 'Fn $ {} (:return 'String) :args $ [] @@ -1966,10 +2041,14 @@ (:message sid msg) let action $ decode-map-as (parse-cirru-edn msg) (:: 'Map 'Tag 'Dynamic) - case-default (&map:get action :kind) (println "|unknown action:" action) + case-default + .unwrap-or (get action :kind) nil + println "|unknown action:" action :op $ dispatch! - decode-map-as (&map:get action :op) 'Tag - &map:get action :data + decode-map-as + .unwrap-or (get action :op) nil + , 'Tag + .unwrap-or (get action :data) nil , sid (:disconnect sid) do (println "|Client closed!") (dispatch! :session/disconnect nil sid) @@ -2110,14 +2189,18 @@ session-map $ decode-map-as option:unwrap-or session $ {} :: 'Map 'Tag 'Dynamic - user-id $ &map:get session-map :user-id + user-id $ .unwrap-or (get session-map :user-id) nil logged-in? $ non-nil? user-id - router $ decode-map-as (&map:get session-map :router) (:: 'Map 'Tag 'Dynamic) + router $ decode-map-as + .unwrap-or (get session-map :router) nil + :: 'Map 'Tag 'Dynamic base-data $ {} (:logged-in? logged-in?) (:session session-map) :reel-length $ count records-list - sessions $ decode-map-as (&map:get db-map :sessions) + sessions $ decode-map-as + .unwrap-or (get db-map :sessions) nil :: 'Map 'Number $ :: 'Map 'Tag 'Dynamic - users $ decode-map-as (&map:get db-map :users) + users $ decode-map-as + .unwrap-or (get db-map :users) nil :: 'Map 'String $ :: 'Map 'Tag 'Dynamic merge base-data $ decode-map-as if logged-in? @@ -2125,17 +2208,19 @@ :user $ twig-user $ option:unwrap-or get-in db-map $ [] :users user-id {} - :router $ assoc router :data $ case-default (&map:get router :name) ({}) + :router $ assoc router :data $ case-default + .unwrap-or (get router :name) nil + {} :home $ {} - :articles $ twig-articles $ &map:get db-map :articles + :articles $ twig-articles $ .unwrap-or (get db-map :articles) nil :focuses $ twig-focuses :article-id sessions users :article $ let - article-id $ &map:get session-map :article-id + article-id $ .unwrap-or (get session-map :article-id) nil {} :article $ option:unwrap-or get-in db-map $ [] :articles article-id {} - :paragraph-id $ &map:get session-map :paragraph-id + :paragraph-id $ .unwrap-or (get session-map :paragraph-id) nil :focuses $ twig-focuses :paragraph-id sessions users :members $ twig-members article-id sessions users :profile $ twig-profile sessions users @@ -2158,11 +2243,21 @@ :sessions $ {} $ 7 session-value :users $ {} projection $ twig-container db (Option :some session-value) ([] |record-a |record-b) - assert= false $ &map:get projection :logged-in? - assert= 2 $ &map:get projection :reel-length - assert= session-value $ &map:get projection :session - assert= nil $ &map:get projection :color - assert= nil $ &map:get projection :count + assert= false $ match (get projection :logged-in?) + (:some lookup-value) lookup-value + (:none) nil + assert= 2 $ match (get projection :reel-length) + (:some lookup-value) lookup-value + (:none) nil + assert= session-value $ match (get projection :session) + (:some lookup-value) lookup-value + (:none) nil + assert= nil $ match (get projection :color) + (:some lookup-value) lookup-value + (:none) nil + assert= nil $ match (get projection :count) + (:some lookup-value) lookup-value + (:none) nil :tags $ #{} :business :unit %{} 'TestEntry (:name |rejects-non-list-records-before-db-access) :code $ quote $ assert= true @@ -2185,11 +2280,13 @@ filter-kv sessions-map $ fn (k session) let session-map $ decode-map-as session $ :: 'Map 'Tag 'Dynamic - router-map $ decode-map-as (&map:get session-map :router) (:: 'Map 'Tag 'Dynamic) + router-map $ decode-map-as + .unwrap-or (get session-map :router) nil + :: 'Map 'Tag 'Dynamic and - = :article $ &map:get router-map :name - non-nil? $ &map:get session-map :user-id - non-nil? $ &map:get session-map router-id + = :article $ .unwrap-or (get router-map :name) nil + non-nil? $ .unwrap-or (get session-map :user-id) nil + non-nil? $ .unwrap-or (get session-map router-id) nil :: 'Map 'Number $ :: 'Map 'Tag 'Dynamic result $ -> filtered (map-entries) map $ fn (entry) @@ -2199,8 +2296,10 @@ let k $ :key entry session-map $ :value entry - article-id $ &map:get session-map router-id - user-id $ decode-map-as (&map:get session-map :user-id) 'String + article-id $ .unwrap-or (get session-map router-id) nil + user-id $ decode-map-as + .unwrap-or (get session-map :user-id) nil + , 'String {} (:id article-id) :name $ option:unwrap-or get-in users-map $ [] user-id :name @@ -2210,7 +2309,9 @@ hint-fn $ {} :args $ [] $ :: 'Map 'Tag 'Dynamic :return 'String - decode-map-as (&map:get x :id) 'String + decode-map-as + .unwrap-or (get x :id) nil + , 'String , result :examples $ [] :schema $ :: 'Fn $ {} (:return 'Dynamic) @@ -2225,16 +2326,18 @@ filter-kv sessions-map $ fn (k session) let session-map $ decode-map-as session $ :: 'Map 'Tag 'Dynamic - router $ decode-map-as (&map:get session-map :router) (:: 'Map 'Tag 'Dynamic) + router $ decode-map-as + .unwrap-or (get session-map :router) nil + :: 'Map 'Tag 'Dynamic and - = :article $ &map:get router :name - = article-id $ &map:get session-map :article-id + = :article $ .unwrap-or (get router :name) nil + = article-id $ .unwrap-or (get session-map :article-id) nil :: 'Map 'Number $ :: 'Map 'Tag 'Dynamic -> filtered $ .map-kv $ fn (k session) let session-map $ decode-map-as session $ :: 'Map 'Tag 'Dynamic [] k $ match - read-user-name users-map $ &map:get session-map :user-id + read-user-name users-map $ .unwrap-or (get session-map :user-id) nil (:none) nil (:some name) name :examples $ [] @@ -2247,15 +2350,17 @@ users-map $ decode-map-as users $ :: 'Map 'String (:: 'Map 'Tag 'Dynamic) filtered $ decode-map-as filter-kv sessions-map $ fn (k session) - non-nil? $ &map:get - decode-map-as session $ :: 'Map 'Tag 'Dynamic - , :user-id + non-nil? $ .unwrap-or + get + decode-map-as session $ :: 'Map 'Tag 'Dynamic + , :user-id + , nil :: 'Map 'Number $ :: 'Map 'Tag 'Dynamic filter-map-kv filtered $ fn (k session) let session-map $ decode-map-as session $ :: 'Map 'Tag 'Dynamic MapEntryDecision :keep k $ match - read-user-name users-map $ &map:get session-map :user-id + read-user-name users-map $ .unwrap-or (get session-map :user-id) nil (:none) nil (:some name) name :examples $ [] @@ -2316,10 +2421,26 @@ :users $ {} $ |u-1 {} $ :name |Ada connected $ updater db (:: :session/connect nil) 42 |op-1 1 - sessions $ decode-map-as (&map:get connected :sessions) (:: 'Map 'Number 'Dynamic) - session-value $ decode-map-as (&map:get sessions 42) (:: 'Map 'Tag 'Dynamic) - assert= 42 $ &map:get session-value :id - assert= (&map:get db :users) (&map:get connected :users) + sessions $ decode-map-as + match (get connected :sessions) + (:some lookup-value) lookup-value + (:none) nil + :: 'Map 'Number 'Dynamic + session-value $ decode-map-as + match (get sessions 42) + (:some lookup-value) lookup-value + (:none) nil + :: 'Map 'Tag 'Dynamic + assert= 42 $ match (get session-value :id) + (:some lookup-value) lookup-value + (:none) nil + assert= + match (get db :users) + (:some lookup-value) lookup-value + (:none) nil + match (get connected :users) + (:some lookup-value) lookup-value + (:none) nil assert= db $ updater connected (:: :session/disconnect nil) 42 |op-2 2 :tags $ #{} :unit :updater %{} 'TestEntry (:name |unknown-operation-preserves-root-map) @@ -2340,8 +2461,10 @@ let op-map $ decode-map-as op-data $ :: 'Map 'Tag 'Dynamic assoc-in db - [] :articles (&map:get op-map :id) :title - &map:get op-map :title + [] :articles + .unwrap-or (get op-map :id) nil + , :title + .unwrap-or (get op-map :title) nil :examples $ [] :schema $ :: 'Fn $ {} (:return 'Dynamic) :args $ [] 'Dynamic 'Dynamic 'Number 'String 'Number @@ -2445,8 +2568,12 @@ :code $ quote $ defn move (db op-data sid op-id op-time) let op-map $ decode-map-as op-data $ :: 'Map 'Tag 'String - target-key $ &map:get op-map :target - base-key $ &map:get op-map :base + target-key $ match (get op-map :target) + (:some lookup-value) lookup-value + (:none) (raise "|paragraph move requires :target") + base-key $ match (get op-map :base) + (:some lookup-value) lookup-value + (:none) (raise "|paragraph move requires :base") article-id $ option:unwrap-or get-in db $ [] :sessions sid :article-id , nil @@ -2459,7 +2586,9 @@ > (&str:compare target-key base-key) 0 bisection-util/key-before paragraphs base-key bisection-util/key-after paragraphs base-key - paragraph $ &map:get paragraphs target-key + paragraph $ match (get paragraphs target-key) + (:some lookup-value) lookup-value + (:none) nil -> db update-in ([] :articles article-id :paragraphs) fn (paragraphs) @@ -2467,12 +2596,50 @@ option:unwrap-or paragraphs $ {} assoc new-key paragraph dissoc target-key - update ([] :sessions sid :paragraph-id) + update-in ([] :sessions sid :paragraph-id) fn (old-key) - if (non-nil? old-key) new-key nil + if + non-nil? $ .unwrap-or old-key nil + , new-key nil :examples $ [] :schema $ :: 'Fn $ {} (:return 'Dynamic) :args $ [] 'Dynamic 'Dynamic 'Number 'String 'Number + :tests $ [] $ %{} 'TestEntry + :name |move-preserves-content-and-updates-numeric-session-cursor + :code $ quote $ let + original $ {} + :sessions $ {} $ 7 + {} (:article-id |article) (:paragraph-id |b) + :articles $ {} $ |article + {} $ :paragraphs $ {} + |a $ {} (:content |first) (:time 0) + |b $ {} (:content |second) (:time 0) + next-db $ move original + {} (:target |b) (:base |a) + , 7 |move-op 1 + paragraphs $ decode-map-as + get-in next-db $ [] :articles |article :paragraphs + , .unwrap + :: 'Map 'String 'Dynamic + cursor $ decode-map-as + get-in next-db $ [] :sessions 7 :paragraph-id + , .unwrap + , 'String + rejected? $ fn (payload) + try + do (move original payload 7 |move-op 1) false + fn (message) true + assert= 2 $ paragraphs.len + assert= false $ paragraphs.contains-key? |b + assert= |second $ + get-in paragraphs $ [] cursor :content + , .unwrap + assert= |first $ + get-in paragraphs $ [] |a :content + , .unwrap + assert= true $ rejected? $ {} (:base |a) + assert= true $ rejected? $ {} (:target |b) + :tags $ #{} :business :unit 'para-prepend $ %{} 'CodeEntry (:doc |) :code $ quote $ defn para-prepend (db op-data sid op-id op-time) let @@ -2523,9 +2690,12 @@ |T $ {} $ :content "|删除对象" |g $ {} $ :content "|保留对象🌱" next-db $ remove-one db |T 7 |operation-id 42 - assert= nil $ &map:get - option:unwrap $ get-in next-db $ [] :sessions 7 - , :paragraph-id + assert= nil $ match + get + option:unwrap $ get-in next-db $ [] :sessions 7 + , :paragraph-id + (:some lookup-value) lookup-value + (:none) nil assert= option:unwrap $ get-in db $ [] :articles |a :paragraphs |g option:unwrap $ get-in next-db $ [] :articles |a :paragraphs |g @@ -2547,9 +2717,12 @@ |T $ {} $ :content "|删除对象" |g $ {} $ :content "|保留对象🌱" next-db $ remove-one db |absent 7 |operation-id 42 - assert= nil $ &map:get - option:unwrap $ get-in next-db $ [] :sessions 7 - , :paragraph-id + assert= nil $ match + get + option:unwrap $ get-in next-db $ [] :sessions 7 + , :paragraph-id + (:some lookup-value) lookup-value + (:none) nil assert= option:unwrap $ get-in db $ [] :articles |a :paragraphs |g option:unwrap $ get-in next-db $ [] :articles |a :paragraphs |g @@ -2569,12 +2742,12 @@ get-in db $ [] :sessions sid :article-id , nil update-in db - [] :articles article-id :paragraphs $ &map:get op-map :id + [] :articles article-id :paragraphs $ .unwrap-or (get op-map :id) nil fn (paragraph) -> option:unwrap-or paragraph $ {} - assoc :time $ &map:get op-map :time - assoc :content $ &map:get op-map :text + assoc :time $ .unwrap-or (get op-map :time) nil + assoc :content $ .unwrap-or (get op-map :text) nil :examples $ [] :schema $ :: 'Fn $ {} (:return 'Dynamic) :args $ [] 'Dynamic 'Dynamic 'Number 'String 'Number @@ -2610,7 +2783,9 @@ :code $ quote $ defn remove-message (db op-data sid op-id op-time) let payload $ decode-map-as op-data $ :: 'Map 'Tag 'Dynamic - message-id $ decode-map-as (&map:get payload :id) 'String + message-id $ decode-map-as + .unwrap-or (get payload :id) nil + , 'String update-in db ([] :sessions sid :messages) fn (messages) dissoc @@ -2711,9 +2886,11 @@ :: 'Map 'String $ :: 'Map 'Tag 'Dynamic maybe-user $ -> users (vals) (.to-list) find $ fn (user) - = username $ &map:get - decode-map-as user $ :: 'Map 'Tag 'Dynamic - , :name + = username $ .unwrap-or + get + decode-map-as user $ :: 'Map 'Tag 'Dynamic + , :name + , nil update-in db-map ([] :sessions sid) fn (session) let @@ -2724,8 +2901,9 @@ let user-map $ decode-map-as (option:unwrap maybe-user) (:: 'Map 'Tag 'Dynamic) if - = (md5 password) (&map:get user-map :password) - assoc session-map :user-id $ &map:get user-map :id + = (md5 password) + .unwrap-or (get user-map :password) nil + assoc session-map :user-id $ .unwrap-or (get user-map :id) nil update session-map :messages $ fn (messages) assoc decode-map-as messages $ :: 'Map 'String 'Dynamic @@ -2802,9 +2980,11 @@ maybe-user $ find .to-list $ vals users fn (user) - = username $ &map:get - decode-map-as user $ :: 'Map 'Tag 'Dynamic - , :name + = username $ .unwrap-or + get + decode-map-as user $ :: 'Map 'Tag 'Dynamic + , :name + , nil if (option:some? maybe-user) update-in db-map ([] :sessions sid :messages) fn (messages) diff --git a/deps.cirru b/deps.cirru index 403028c..c6e4ad1 100644 --- a/deps.cirru +++ b/deps.cirru @@ -1,5 +1,5 @@ -{} (:calcit-version |0.30.0-alpha.3) +{} (:calcit-version |0.30.0) :version |0.1.1 :dependencies $ {} (|Cumulo/cumulo-reel.calcit |0.0.53) |Cumulo/cumulo-util.calcit |0.0.28 diff --git a/package.json b/package.json index 2a5aa80..ecd94b1 100644 --- a/package.json +++ b/package.json @@ -26,7 +26,7 @@ "vite": "^8.2.2" }, "dependencies": { - "@calcit/procs": "0.30.0-alpha.3", + "@calcit/procs": "0.30.0", "escape-html": "^1.0.3", "nanoid": "^5.1.5" } diff --git a/yarn.lock b/yarn.lock index f351af8..57e23fe 100644 --- a/yarn.lock +++ b/yarn.lock @@ -12,15 +12,15 @@ __metadata: languageName: node linkType: hard -"@calcit/procs@npm:0.30.0-alpha.3": - version: 0.30.0-alpha.3 - resolution: "@calcit/procs@npm:0.30.0-alpha.3" +"@calcit/procs@npm:0.30.0": + version: 0.30.0 + resolution: "@calcit/procs@npm:0.30.0" dependencies: "@calcit/finger-vec": "npm:0.1.1" "@calcit/ternary-tree": "npm:0.0.26" "@cirru/parser.ts": "npm:0.0.10" "@cirru/writer.ts": "npm:^0.1.9" - checksum: 10c0/02ab938f03a2e79b4bdac0e2b982b6cafed57795881c5c9d69d39559cc8624d412d7b9d863813d24d158bc4d922af789ef1b67ff7c2cf86c174279ff19040e71 + checksum: 10c0/4ffe1e9fb7fcc46aae5c95d30e9e20c84582bd527602726ef600322ce3aca9bdcf251f8a3816073dfd4dd7d353d01358df7cd6ba45feba8756dc4dcf17cdb987 languageName: node linkType: hard @@ -725,7 +725,7 @@ __metadata: version: 0.0.0-use.local resolution: "tabletwo@workspace:." dependencies: - "@calcit/procs": "npm:0.30.0-alpha.3" + "@calcit/procs": "npm:0.30.0" bottom-tip: "npm:^0.1.3" cirru-color: "npm:^0.2.4" copy-text-to-clipboard: "npm:^3.2.2" From 6660e3b1332d7c3aab19bbf842d08e97b6fabe64 Mon Sep 17 00:00:00 2001 From: tiye Date: Fri, 16 Oct 2026 10:10:34 +0800 Subject: [PATCH 2/4] =?UTF-8?q?=E5=8D=87=E7=BA=A7=E6=AD=A3=E5=BC=8F=20Bise?= =?UTF-8?q?ction=20Key=200.0.32=20=E5=B9=B6=E5=AF=B9=E9=BD=90=20std=200.2.?= =?UTF-8?q?43?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 3 +++ deps.cirru | 4 ++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 8b625c0..9bc991c 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,9 @@ 段落排序使用类型化 `MapEntry` 保留 String key。拖放缺少 target/base 时明确报错; 同时修复原 `update` 把嵌套 session 路径当单键、移动后未更新编辑光标的问题, 改用 `update-in` 并处理 Option 输入,Number session key 不变。 +排序键依赖使用正式 Bisection Key 0.0.32,并同步正式 std 0.2.43,避免严格图中 +根依赖仍固定旧 std;不使用候选分支或 hash。本次依赖更新不改应用源码、原断言、 +数据库、部署目录或 COS 前缀;Cumulo/Recollect/WS 的另一组升级仍独立等待正式版本。 ```sh caps --strict --ci diff --git a/deps.cirru b/deps.cirru index c6e4ad1..c93fbc6 100644 --- a/deps.cirru +++ b/deps.cirru @@ -9,9 +9,9 @@ |Respo/respo-message.calcit |0.0.34 |Respo/respo-ui.calcit |0.7.39 |Respo/respo.calcit |0.16.120 - |calcit-lang/bisection-key |0.0.30 + |calcit-lang/bisection-key |0.0.32 |calcit-lang/calcit-wss |0.2.34 - |calcit-lang/calcit.std |0.2.41 + |calcit-lang/calcit.std |0.2.43 |calcit-lang/memof |0.0.38 |calcit-lang/recollect |0.0.60 |mvc-works/ws-edn.calcit |0.0.39 From 96ecbdd5ae418a2a8b66fedc189cd9303a928a05 Mon Sep 17 00:00:00 2001 From: tiye Date: Fri, 16 Oct 2026 10:57:56 +0800 Subject: [PATCH 3/4] =?UTF-8?q?=E5=90=8C=E6=AD=A5=E6=AD=A3=E5=BC=8F=20Cumu?= =?UTF-8?q?lo=20Reel=200.0.54=20=E5=8F=8A=E9=85=8D=E5=A5=97=E5=8D=8F?= =?UTF-8?q?=E4=BD=9C=E4=BE=9D=E8=B5=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 4 +++- deps.cirru | 8 ++++---- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 9bc991c..fec4568 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,9 @@ 改用 `update-in` 并处理 Option 输入,Number session key 不变。 排序键依赖使用正式 Bisection Key 0.0.32,并同步正式 std 0.2.43,避免严格图中 根依赖仍固定旧 std;不使用候选分支或 hash。本次依赖更新不改应用源码、原断言、 -数据库、部署目录或 COS 前缀;Cumulo/Recollect/WS 的另一组升级仍独立等待正式版本。 +数据库、部署目录或 COS 前缀。实时协作依赖同步已发布的 Cumulo Reel 0.0.54、 +Recollect 0.0.61、WS-EDN 0.0.40 和 Respo Message 0.0.35,保持根依赖与传递依赖一致。 +此次依赖更新不代表服务器已经准备好,也不触发生产合并部署。 ```sh caps --strict --ci diff --git a/deps.cirru b/deps.cirru index c93fbc6..9943cb5 100644 --- a/deps.cirru +++ b/deps.cirru @@ -1,17 +1,17 @@ {} (:calcit-version |0.30.0) :version |0.1.1 - :dependencies $ {} (|Cumulo/cumulo-reel.calcit |0.0.53) + :dependencies $ {} (|Cumulo/cumulo-reel.calcit |0.0.54) |Cumulo/cumulo-util.calcit |0.0.28 |Respo/alerts.calcit |0.10.52 |Respo/respo-feather.calcit |0.4.26 |Respo/respo-markdown.calcit |0.4.54 - |Respo/respo-message.calcit |0.0.34 + |Respo/respo-message.calcit |0.0.35 |Respo/respo-ui.calcit |0.7.39 |Respo/respo.calcit |0.16.120 |calcit-lang/bisection-key |0.0.32 |calcit-lang/calcit-wss |0.2.34 |calcit-lang/calcit.std |0.2.43 |calcit-lang/memof |0.0.38 - |calcit-lang/recollect |0.0.60 - |mvc-works/ws-edn.calcit |0.0.39 + |calcit-lang/recollect |0.0.61 + |mvc-works/ws-edn.calcit |0.0.40 From c7ec00439d32205acbf985a66fecfdd04cd19175 Mon Sep 17 00:00:00 2001 From: tiye Date: Fri, 16 Oct 2026 10:58:28 +0800 Subject: [PATCH 4/4] =?UTF-8?q?=E8=AE=B0=E5=BD=95=E6=AD=A3=E5=BC=8F?= =?UTF-8?q?=E4=BE=9D=E8=B5=96=E5=9B=BE=E4=B8=8B=200.30.1=20alpha.4=20?= =?UTF-8?q?=E7=9A=84=E5=AE=9E=E9=99=85=E6=A3=80=E6=9F=A5=E8=BE=B9=E7=95=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index fec4568..6dbb352 100644 --- a/README.md +++ b/README.md @@ -98,8 +98,9 @@ calcit calcit.cirru --entry server ## 限制 - 完整交互及迁移验收尚未完成,局部测试不代表整个应用已可发布。 -- 0.30.1 尚未正式发布;alpha.4 的只读探测仍被 WS-EDN、Respo、Recollect - 等已锁定依赖的旧底层接口阻塞。本轮不改用 alpha、不宣称整图已兼容 0.30.1。 +- 0.30.1 尚未正式发布;正式依赖图下 alpha.4 只读检查的服务端 77/77 通过, + 客户端仍有 Respo 和 Alerts 的 6 个失败定义(308 通过、131 阻塞、1 级联)。 + 该静态探测不是 alpha 运行验收;本轮不改用 alpha、不宣称整图已兼容 0.30.1。 - 删除 Confirm 与 Text 弹窗完整内容仍需独立验收。 - 框架 state、投影和宿主的开放边界仍存在,不宣称全应用已静态化。 - Highlight.js 保留原 legacy 调用及弃用提示,依赖中仍有旧 require 写法告警。