diff --git a/.gitignore b/.gitignore index 83448ac..db7e734 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,5 @@ extension/dist .yarn .calcit-snippets/ + +.copilot-tmp/ \ No newline at end of file diff --git a/Agents.md b/Agents.md index a78cf91..2339244 100644 --- a/Agents.md +++ b/Agents.md @@ -1,6 +1,6 @@ -Developer runs `cr js` to watch build JavaScript, and `yarn vite` to start a local server. LLMs edits program by running `cr` commands, and then triggers re-compiling. +Developer runs `cr js` to build JavaScript, and `yarn vite` to start a local server. LLMs edits program by running `cr` commands, and then triggers re-compiling. -要求查看 Calcit 命令行工具的用法: +**必须**查看 Calcit 命令行工具的用法: ```bash cr docs agents --full diff --git a/compact.cirru b/compact.cirru index a7f98ba..b5d39a1 100644 --- a/compact.cirru +++ b/compact.cirru @@ -1,33 +1,34 @@ {} (:about "|file is generated - never edit directly; learn cr edit/tree workflows before changing") (:package |app) :configs $ {} (:init-fn |app.main/main!) (:reload-fn |app.main/reload!) (:version |0.0.1) - :modules $ [] |respo.calcit/ |lilac/ |memof/ |respo-ui.calcit/ |reel.calcit/ |respo-markdown.calcit/ |alerts.calcit/ |respo-feather.calcit/ |genai.calcit/ + :modules $ [] |respo.calcit/ |memof/ |respo-ui.calcit/ |reel.calcit/ |respo-markdown.calcit/ |alerts.calcit/ |respo-feather.calcit/ |genai.calcit/ :entries $ {} :files $ {} |app.comp.container $ %{} :FileEntry :defs $ {} - |*abort-control $ %{} :CodeEntry (:doc |) + |*abort-control $ %{} :CodeEntry (:doc |) (:schema nil) :code $ quote (defatom *abort-control nil) :examples $ [] - |*gen-ai-new $ %{} :CodeEntry (:doc |) + |*gen-ai-new $ %{} :CodeEntry (:doc |) (:schema nil) :code $ quote (defatom *gen-ai-new nil) :examples $ [] - |*image-cache $ %{} :CodeEntry (:doc |) + |*image-cache $ %{} :CodeEntry (:doc |) (:schema nil) :code $ quote (defatom *image-cache nil) :examples $ [] - |*openai $ %{} :CodeEntry (:doc "|called openai sdk, but actually for openrouter") + |*openai $ %{} :CodeEntry (:doc "|called openai sdk, but actually for openrouter") (:schema nil) :code $ quote (defatom *openai nil) :examples $ [] - |append-user-message $ %{} :CodeEntry (:doc |) + |append-user-message $ %{} :CodeEntry (:doc |) (:schema nil) :code $ quote defn append-user-message (messages content) let messages0 $ if (some? messages) messages ([]) conj messages0 $ {} (:role :user) (:content content) :examples $ [] - |call-anthropic-msg! $ %{} :CodeEntry (:doc |) + |call-anthropic-msg! $ %{} :CodeEntry (:doc |) (:schema nil) :code $ quote - defn call-anthropic-msg! (cursor state prompt-text model thinking? d!) (hint-fn async) + defn call-anthropic-msg! (cursor state prompt-text model thinking? d!) + hint-fn $ {} (:async true) if-let abort $ deref *abort-control do (js/console.warn "\"Aborting prev") (.!abort abort) @@ -67,7 +68,8 @@ js/setTimeout $ fn () d! $ :: :states-merge cursor state {} (:answer nil) (:thinking nil) (:loading? true) (:done? false) (:messages messages1) - apply-args () $ fn () (hint-fn async) + apply-args () $ fn () + hint-fn $ {} (:async true) let info $ js-await (.!read reader) value $ wo-js-log (.-value info) @@ -109,11 +111,16 @@ recur xss recur :examples $ [] - |call-flash-imagen-msg! $ %{} :CodeEntry (:doc |) + |call-flash-imagen-msg! $ %{} :CodeEntry (:doc |) (:schema nil) :code $ quote - defn call-flash-imagen-msg! (variant cursor state prompt-text d!) (hint-fn async) + defn call-flash-imagen-msg! (variant cursor state prompt-text d!) + hint-fn $ {} (:async true) if (nil? @*gen-ai-new) - reset! *gen-ai-new $ sdk/new-client (get-gemini-key!) + let + mod $ js-await (js/import |@google/genai) + GoogleGenAI $ .-GoogleGenAI mod + reset! *gen-ai-new $ new GoogleGenAI + js-object $ :apiKey (get-gemini-key!) if-let target $ js/document.querySelector "\".show-image" .!setAttribute target "\"src" "\"" @@ -127,14 +134,20 @@ selected $ js-await (get-selected) gen-ai @*gen-ai-new content $ .!replace prompt-text "\"{{selected}}" (or selected "\"<未找到选中内容>") + abort-signal $ let + abort $ new js/AbortController + reset! *abort-control abort + .-signal abort sdk-result $ js-await - sdk/generate-content! gen-ai $ {} (:model "\"gemini-2.5-flash-image") (:contents content) - :abort-signal $ sdk/make-abort-signal *abort-control - :http-options $ sdk/make-http-options |https://ja.chenyong.life - :response-modalities $ js-array "\"TEXT" "\"IMAGE" + .!generateContent (.-models gen-ai) + js-object (:model "\"gemini-2.5-flash-image") (:contents content) + :config $ js-object (:abortSignal abort-signal) + :httpOptions $ js-object (:baseUrl |https://ja.chenyong.life) + :responseModalities $ js-array "\"TEXT" "\"IMAGE" + parts $ -> sdk-result .-candidates .-0 .-content .-parts *text $ atom "\"" - js-await $ -> (sdk/extract-content-parts sdk-result) - .!forEach $ fn (? chunk _a _b) + js-await $ .!forEach parts + fn (? chunk _a _b) if (some? chunk) if-let text $ .-text chunk @@ -157,11 +170,16 @@ d! $ :: :states cursor -> state (assoc :answer @*text) (assoc :loading? false) (assoc :done? true) :examples $ [] - |call-genai-msg! $ %{} :CodeEntry (:doc |) + |call-genai-msg! $ %{} :CodeEntry (:doc |) (:schema nil) :code $ quote - defn call-genai-msg! (variant cursor state prompt-text search? think? d! *text *thinking-text) (hint-fn async) + defn call-genai-msg! (variant cursor state prompt-text search? think? d! *text *thinking-text) + hint-fn $ {} (:async true) if (nil? @*gen-ai-new) - reset! *gen-ai-new $ sdk/new-client (get-gemini-key!) + let + mod $ js-await (js/import |@google/genai) + GoogleGenAI $ .-GoogleGenAI mod + reset! *gen-ai-new $ new GoogleGenAI + js-object $ :apiKey (get-gemini-key!) if-let abort $ deref *abort-control do (js/console.warn "\"Aborting prev") (.!abort abort) @@ -176,18 +194,35 @@ has-url? $ or (.!includes prompt-text "\"http://") (.!includes prompt-text "\"https://") messages0 $ or (:messages state) ([]) messages1 $ upsert-assistant-message messages0 "\"" nil + abort-signal $ let + abort $ new js/AbortController + reset! *abort-control abort + .-signal abort + tools $ -> + js-array + if search? + js-object $ :googleSearch (js-object) + , js/undefined + if has-url? + js-object $ :urlContext (js-object) + , js/undefined + .!filter $ fn (x _idx _arr) x sdk-result $ js-await - sdk/generate-content-stream! gen-ai $ {} (:model model) - :contents $ sdk/messages->contents messages0 - :thinking-config $ if think? - sdk/make-thinking-config - get-env "\"think-budget" $ if pro? 3200 800 - , true - sdk/make-thinking-config 0 false - :tools $ sdk/make-search-tools search? has-url? - :abort-signal $ sdk/make-abort-signal *abort-control - :http-options $ sdk/make-http-options |https://ja.chenyong.life - :response-mime-type $ if json? "\"application/json" nil + .!generateContentStream (.-models gen-ai) + js-object (:model model) + :contents $ messages->gemini messages0 + :config $ js-object + :thinkingConfig $ if think? + js-object + :thinkingBudget $ get-env "\"think-budget" (if pro? 3200 800) + :includeThoughts true + js-object (:thinkingBudget 0) (:includeThoughts false) + :tools $ if + > (.-length tools) 0 + , tools js/undefined + :abortSignal abort-signal + :httpOptions $ js-object (:baseUrl |https://ja.chenyong.life) + :responseMimeType $ if json? "\"application/json" js/undefined do js/setTimeout $ fn () d! $ :: :states-merge cursor state @@ -196,9 +231,11 @@ fn (? chunk) if (some? chunk) let - info $ sdk/extract-stream-chunk chunk - is-thinking? $ :thinking? info - text $ or (:text info) |__BLANK__ + part $ -> chunk .-candidates .-0 .-content .-parts .-0 + is-thinking? $ if (some? part) (.-thought part) false + text $ or + if (some? part) (.-text part) (.-text chunk) + -> chunk .-promptFeedback .-blockReason if is-thinking? (swap! *thinking-text str text) (swap! *text str text) d! $ :: :states-merge cursor state {} (:answer @*text) (:thinking @*thinking-text) (:loading? false) (:done? false) @@ -210,11 +247,16 @@ {} (:answer @*text) (:thinking @*thinking-text) (:loading? false) (:done? true) :messages $ upsert-assistant-message messages1 @*text @*thinking-text :examples $ [] - |call-imagen-4-msg! $ %{} :CodeEntry (:doc |) + |call-imagen-4-msg! $ %{} :CodeEntry (:doc |) (:schema nil) :code $ quote - defn call-imagen-4-msg! (variant cursor state prompt-text d!) (hint-fn async) + defn call-imagen-4-msg! (variant cursor state prompt-text d!) + hint-fn $ {} (:async true) if (nil? @*gen-ai-new) - reset! *gen-ai-new $ sdk/new-client (get-gemini-key!) + let + mod $ js-await (js/import |@google/genai) + GoogleGenAI $ .-GoogleGenAI mod + reset! *gen-ai-new $ new GoogleGenAI + js-object $ :apiKey (get-gemini-key!) if-let target $ js/document.querySelector "\".show-image" .!removeAttribute target "\"src" @@ -227,13 +269,19 @@ let selected $ js-await (get-selected) gen-ai @*gen-ai-new + abort-signal $ let + abort $ new js/AbortController + reset! *abort-control abort + .-signal abort response $ js-await - sdk/generate-images! gen-ai $ {} (:model "\"imagen-4.0-generate-001") (:prompt prompt-text) (:number-of-images 1) (:include-rai-reason true) - :abort-signal $ sdk/make-abort-signal *abort-control - :http-options $ sdk/make-http-options |https://ja.chenyong.life + .!generateImages (.-models gen-ai) + js-object (:model "\"imagen-4.0-generate-001") (:prompt prompt-text) + :config $ js-object (:numberOfImages 1) (:includeRaiReason true) + :httpOptions $ js-object (:baseUrl |https://ja.chenyong.life) + :signal abort-signal *text $ atom "\"" if-let - image-data $ sdk/extract-image-bytes response + image-data $ -> response .-generatedImages .-0 .-image .-imageBytes let image-blob $ base64ToBlob image-data url $ js/URL.createObjectURL image-blob @@ -246,15 +294,19 @@ d! $ :: :states cursor -> state (assoc :answer @*text) (assoc :loading? false) (assoc :done? true) :examples $ [] - |call-openrouter! $ %{} :CodeEntry (:doc |) + |call-openrouter! $ %{} :CodeEntry (:doc |) (:schema nil) :code $ quote - defn call-openrouter! (cursor state prompt-text variant thinking? d! *text) (hint-fn async) + defn call-openrouter! (cursor state prompt-text variant thinking? d! *text) + hint-fn $ {} (:async true) if (nil? @*openai) - reset! *openai $ new OpenAI - js-object (:baseURL "\"https://openrouter.ai/api/v1") - :apiKey $ get-openrouter-key! - :defaultHeaders $ js-object - :dangerouslyAllowBrowser true + let + mod $ js-await (js/import |openai) + OpenAI $ .-default mod + reset! *openai $ new OpenAI + js-object (:baseURL "\"https://openrouter.ai/api/v1") + :apiKey $ get-openrouter-key! + :defaultHeaders $ js-object + :dangerouslyAllowBrowser true if-let abort $ deref *abort-control do (js/console.warn "\"Aborting prev") (.!abort abort) @@ -286,7 +338,7 @@ d! $ :: :states-merge cursor state {} (:answer nil) (:thinking nil) (:loading? true) (:done? false) (:messages messages1) js-await $ js-for-await sdk-result - fn (? chunk) (; js/console.log "\"[CHUNK]" chunk) + fn (? chunk) if (some? chunk) do swap! *text str $ -> chunk .-choices .-0 .-delta .-content (or "\"") @@ -300,12 +352,12 @@ {} (:answer @*text) (:loading? false) (:done? true) :messages $ upsert-assistant-message messages1 @*text nil :examples $ [] - |clear-image-cache! $ %{} :CodeEntry (:doc |) + |clear-image-cache! $ %{} :CodeEntry (:doc |) (:schema nil) :code $ quote defn clear-image-cache! () $ if-let (url @*image-cache) do (js/URL.revokeObjectURL url) (reset! *image-cache nil) :examples $ [] - |comp-abort $ %{} :CodeEntry (:doc |) + |comp-abort $ %{} :CodeEntry (:doc |) (:schema nil) :code $ quote defn comp-abort (t) span @@ -320,7 +372,7 @@ =< 8 nil <> "\"✕" style-abort-close :examples $ [] - |comp-container $ %{} :CodeEntry (:doc |) + |comp-container $ %{} :CodeEntry (:doc |) (:schema nil) :code $ quote defcomp comp-container (reel) let @@ -464,6 +516,16 @@ .show reply-plugin d! $ fn (text) submit-message! cursor state text (:search? message-box-state) (:think? message-box-state) model d! <> |Reply + if (:focus-mode? message-box-state) nil $ a + {} (:class-name style-focus-link) (:inner-text |Focus) + :on-click $ fn (e d!) + let + focused $ .-activeElement js/document + do + if (some? focused) (.!blur focused) + d! + :cursor $ >> states :message-box + assoc message-box-state :focus-mode? true , nil if (:loading? state) div ({}) (memof1-call-by :abort-loading comp-abort "\"Loading...") @@ -516,7 +578,7 @@ if dev? $ comp-reel (>> states :reel) reel ({}) if dev? $ comp-inspect "\"Store" store nil :examples $ [] - |comp-fill $ %{} :CodeEntry (:doc |) + |comp-fill $ %{} :CodeEntry (:doc |) (:schema nil) :code $ quote defcomp comp-fill (text) div @@ -526,136 +588,183 @@ js-object (:action |fill-text) (:text text) comp-i :send 12 :currentColor :examples $ [] - |comp-message-box $ %{} :CodeEntry (:doc |) + |comp-message-box $ %{} :CodeEntry (:doc |) (:schema nil) :code $ quote defcomp comp-message-box (states picker-el on-submit model) let cursor $ :cursor states state $ either (:data states) - {} (:content "\"") (:search? false) (:think? false) + {} (:content "\"") (:search? false) (:think? false) (:focus-mode? false) [] (effect-focus) (on-fill cursor state on-submit) div {} $ :class-name (str-spaced css/center style-message-box-panel) div {} $ :class-name (str-spaced css/column style-message-box) - textarea $ {} - :value $ :content state - :placeholder "\"Prompt to try LLM..." - :id "\"message" - :class-name $ str-spaced css/textarea css/font-code! style-textbox - :on-input $ fn (e d!) - d! cursor $ assoc state :content (:value e) - :on-keydown $ fn (e d!) - if - and - = 13 $ :keycode e - or (:meta? e) (:ctrl? e) - on-submit (:content state) (:search? state) (:think? state) d! - :on-focus $ fn (e d!) - let - target $ .-target (:event e) - box $ .-parentElement (.-parentElement target) - class-list $ .-classList target - box-class $ .-classList box - if - not $ .!contains class-list "\"focus-within" - .!add class-list "\"focus-within" - if - not $ .!contains box-class "\"focus-within" - .!add box-class "\"focus-within" - :on-blur $ fn (e d!) - let - target $ .-target (:event e) - box $ .-parentElement (.-parentElement target) - class-list $ .-classList target - box-class $ .-classList box - if (.!contains class-list "\"focus-within") (.!remove class-list "\"focus-within") - if (.!contains box-class "\"focus-within") (.!remove box-class "\"focus-within") - =< nil 4 - div - {} $ :class-name css/row-parted - if - not $ blank? (:content state) - comp-close $ {} (:class-name style-clear) - :on-click $ fn (e d!) - d! cursor $ assoc state :content "\"" - -> (js/document.querySelector "\"#message") (.!focus) - span $ {} (:class-name style-clear) + if (:focus-mode? state) div - {} $ :class-name (str-spaced css/row style-gap12) - , picker-el + {} + :class-name $ str-spaced css/font-code! style-focus-box style-textbox-compact + :on-click $ fn (e d!) + do + d! cursor $ assoc state :focus-mode? false + js/setTimeout + fn () $ -> (js/document.querySelector "\"#message") (.!focus) + , 0 + <> $ if + blank? $ :content state + , "\"Click to expand and type..." (:content state) + textarea $ {} + :value $ :content state + :placeholder "\"Prompt to try LLM..." + :id "\"message" + :class-name $ str-spaced css/textarea css/font-code! style-textbox + :on-input $ fn (e d!) + d! cursor $ assoc state :content (:value e) + :on-keydown $ fn (e d!) if - contains? (#{} :gemini-flash :gemini-3.1-flash-lite-preview) model - div - {} - :class-name $ str-spaced css/row style-checkbox - :on-click $ fn (e d!) - d! cursor $ assoc state :think? - not $ :think? state - input $ {} - :checked $ :think? state - :type "\"checkbox" - <> "\"Think" css/font-fancy - , nil - div - {} - :class-name $ str-spaced css/row style-checkbox + and + = 13 $ :keycode e + or (:meta? e) (:ctrl? e) + on-submit (:content state) (:search? state) (:think? state) d! + :on-focus $ fn (e d!) + let + target $ .-target (:event e) + box $ .-parentElement (.-parentElement target) + class-list $ .-classList target + box-class $ .-classList box + if + not $ .!contains class-list "\"focus-within" + .!add class-list "\"focus-within" + if + not $ .!contains box-class "\"focus-within" + .!add box-class "\"focus-within" + :on-blur $ fn (e d!) + let + target $ .-target (:event e) + box $ .-parentElement (.-parentElement target) + class-list $ .-classList target + box-class $ .-classList box + if (.!contains class-list "\"focus-within") (.!remove class-list "\"focus-within") + if (.!contains box-class "\"focus-within") (.!remove box-class "\"focus-within") + if + not $ :focus-mode? state + do (=< nil 4) + div + {} $ :class-name css/row-parted + if + not $ blank? (:content state) + comp-close $ {} (:class-name style-clear) :on-click $ fn (e d!) - d! cursor $ assoc state :search? - not $ :search? state - input $ {} - :checked $ :search? state - :type "\"checkbox" - <> "\"Search" css/font-fancy - button $ {} - :class-name $ str-spaced css/button style-submit - :inner-text "\"Submit" - :on-click $ fn (e d!) - ; println $ :content state - on-submit (:content state) (:search? state) (:think? state) d! + d! cursor $ assoc state :content "\"" + -> (js/document.querySelector "\"#message") (.!focus) + span $ {} (:class-name style-clear) + div + {} $ :class-name (str-spaced css/row style-gap12) + , picker-el + if + contains? (#{} :gemini-flash :gemini-3.1-flash-lite-preview) model + div + {} + :class-name $ str-spaced css/row style-checkbox + :on-click $ fn (e d!) + d! cursor $ assoc state :think? + not $ :think? state + input $ {} + :checked $ :think? state + :type "\"checkbox" + <> "\"Think" css/font-fancy + , nil + div + {} + :class-name $ str-spaced css/row style-checkbox + :on-click $ fn (e d!) + d! cursor $ assoc state :search? + not $ :search? state + input $ {} + :checked $ :search? state + :type "\"checkbox" + <> "\"Search" css/font-fancy + button $ {} + :class-name $ str-spaced css/button style-submit + :inner-text "\"Submit" + :on-click $ fn (e d!) + on-submit (:content state) (:search? state) (:think? state) d! + , nil :examples $ [] - |comp-sessions-modal $ %{} :CodeEntry (:doc |) + |comp-sessions-modal $ %{} :CodeEntry (:doc |) (:schema nil) :code $ quote defcomp comp-sessions-modal (sessions on-select on-close) - div - {} $ :class-name (str-spaced css/column css/gap8 style-sessions-list) - if (empty? sessions) - div - {} $ :style - {} (:padding |12px) - :color $ hsl 0 0 60 - <> "|No history sessions" - list-> - {} $ :class-name css/column - -> sessions (.!reverse) - map $ fn (session) - let - session-id $ :id session - created-at $ :created-at session - preview $ :preview session - date-str $ .!toLocaleString (new js/Date created-at) - [] session-id $ div - {} $ :class-name style-session-item - div - {} - :style $ {} (:flex |1) (:cursor :pointer) (:min-width 0) (:overflow :hidden) - :on-click $ fn (e d!) (on-select session-id d!) (on-close d!) + let + history-items $ foldl sessions 0 + fn (acc session) + + acc $ count + or (:messages session) ([]) + div + {} $ :class-name (str-spaced css/column css/gap8 style-sessions-list) + if (empty? sessions) + div + {} $ :style + {} (:padding |12px) + :color $ hsl 0 0 60 + <> "|No history sessions" + list-> + {} $ :class-name css/column + -> sessions (.!reverse) + map $ fn (session) + let + session-id $ :id session + created-at $ :created-at session + preview $ :preview session + date-str $ .!toLocaleString (new js/Date created-at) + [] session-id $ div + {} $ :class-name style-session-item div - {} $ :style - {} (:font-size |12px) - :color $ hsl 0 0 60 - <> date-str + {} + :style $ {} (:flex |1) (:cursor :pointer) (:min-width 0) (:overflow :hidden) + :on-click $ fn (e d!) (on-select session-id d!) (on-close d!) + div + {} $ :style + {} (:font-size |12px) + :color $ hsl 0 0 60 + <> date-str + div + {} $ :style + {} (:margin-top |4px) (:white-space :nowrap) (:overflow :hidden) (:text-overflow :ellipsis) (:max-height |1.2em) (:line-height |1.2) + <> preview div - {} $ :style - {} (:margin-top |4px) (:white-space :nowrap) (:overflow :hidden) (:text-overflow :ellipsis) (:max-height |1.2em) (:line-height |1.2) - <> preview - div - {} (:class-name style-delete-button) - :on-click $ fn (e d!) (-> e :event .!stopPropagation) - d! $ :: :remove-session session-id - <> "|✕" + {} (:class-name style-delete-button) + :on-click $ fn (e d!) (-> e :event .!stopPropagation) + d! $ :: :remove-session session-id + <> "|✕" + if + > (count sessions) 0 + div + {} + :class-name $ str-spaced css/column css/gap8 + :style $ {} (:padding "|0 12px 12px 12px") + div + {} $ :class-name (str-spaced css/row-parted) + div + {} $ :class-name (str-spaced css/row css/gap8) + a $ {} (:class-name style-clear) (:inner-text |Data) + :on-click $ fn (e d!) (tab-echo! sessions :edn) + a $ {} (:class-name style-clear) (:inner-text |Download) + :on-click $ fn (e d!) (download-sessions! sessions) + if + > (count sessions) 0 + a $ {} (:class-name style-clear) (:inner-text "|Clear all") + :on-click $ fn (e d!) + let + proceed? $ if (> history-items 10) + js/confirm $ str-spaced |Clear history-items "|messages from history?" + , true + when proceed? $ d! (:: :clear-sessions) + span $ {} + div $ {} + :style $ {} (:height 200) + , nil :examples $ [] - |create-session $ %{} :CodeEntry (:doc |) + |create-session $ %{} :CodeEntry (:doc |) (:schema nil) :code $ quote defn create-session (messages model) let @@ -674,14 +783,29 @@ .!slice first-msg 0 end :is-history? false :examples $ [] - |effect-focus $ %{} :CodeEntry (:doc |) + |download-sessions! $ %{} :CodeEntry (:doc |) (:schema nil) + :code $ quote + defn download-sessions! (sessions) + let + content $ format-cirru-edn sessions + blob $ new js/Blob (js-array content) + js-object $ :type |application/edn;charset=utf-8 + url $ js/URL.createObjectURL blob + link $ js/document.createElement |a + filename $ str |sessions- (js/Date.now) |.cirru + do (.!setAttribute link |href url) (.!setAttribute link |download filename) (.!appendChild js/document.body link) (.!click link) (.!remove link) + js/setTimeout + fn () $ js/URL.revokeObjectURL url + , 0 + :examples $ [] + |effect-focus $ %{} :CodeEntry (:doc |) (:schema nil) :code $ quote defeffect effect-focus () (action el at?) when (= action :mount) js/setTimeout $ fn () .!select $ .!querySelector el "\"textarea" :examples $ [] - |first-line $ %{} :CodeEntry (:doc "|last message from error contains a line starts with \"data: \" and an extra error message. In order that JSON is parsed correctly, only first line is used now.") + |first-line $ %{} :CodeEntry (:doc "|last message from error contains a line starts with \"data: \" and an extra error message. In order that JSON is parsed correctly, only first line is used now.") (:schema nil) :code $ quote defn first-line (tt) let @@ -693,11 +817,11 @@ js/console.warn "\"Droping some unexpected lines:" $ .!slice lines 1 .-0 lines :examples $ [] - |generate-session-id $ %{} :CodeEntry (:doc |) + |generate-session-id $ %{} :CodeEntry (:doc |) (:schema nil) :code $ quote defn generate-session-id () $ str (js/Date.now) :examples $ [] - |get-anthropic-key! $ %{} :CodeEntry (:doc |) + |get-anthropic-key! $ %{} :CodeEntry (:doc |) (:schema nil) :code $ quote defn get-anthropic-key! () $ let key $ js/localStorage.getItem "\"claude-key" @@ -710,7 +834,7 @@ , v , key :examples $ [] - |get-deepinfra-key! $ %{} :CodeEntry (:doc |) + |get-deepinfra-key! $ %{} :CodeEntry (:doc |) (:schema nil) :code $ quote defn get-deepinfra-key! () $ let key $ js/localStorage.getItem "\"deepinfra-key" @@ -723,7 +847,7 @@ , v , key :examples $ [] - |get-gemini-key! $ %{} :CodeEntry (:doc |) + |get-gemini-key! $ %{} :CodeEntry (:doc |) (:schema nil) :code $ quote defn get-gemini-key! () $ let key $ js/localStorage.getItem "\"gemini-key" @@ -736,7 +860,7 @@ , v , key :examples $ [] - |get-openrouter-key! $ %{} :CodeEntry (:doc |) + |get-openrouter-key! $ %{} :CodeEntry (:doc |) (:schema nil) :code $ quote defn get-openrouter-key! () $ let key $ js/localStorage.getItem "\"openrouter-key" @@ -749,12 +873,12 @@ , v , key :examples $ [] - |json-pattern? $ %{} :CodeEntry (:doc |) + |json-pattern? $ %{} :CodeEntry (:doc |) (:schema nil) :code $ quote defn json-pattern? (text) or (.!startsWith text "\"{") (.!startsWith text "\"[") :examples $ [] - |messages->anthropic $ %{} :CodeEntry (:doc |) + |messages->anthropic $ %{} :CodeEntry (:doc |) (:schema nil) :code $ quote defn messages->anthropic (messages) to-js-data $ map (or messages []) @@ -765,7 +889,7 @@ , |assistant |user :content $ :content m :examples $ [] - |messages->gemini $ %{} :CodeEntry (:doc |) + |messages->gemini $ %{} :CodeEntry (:doc |) (:schema nil) :code $ quote defn messages->gemini (messages) let @@ -779,7 +903,7 @@ :parts $ [] {} $ :text (:content m) :examples $ [] - |messages->openai $ %{} :CodeEntry (:doc |) + |messages->openai $ %{} :CodeEntry (:doc |) (:schema nil) :code $ quote defn messages->openai (messages) let @@ -792,11 +916,11 @@ , |assistant |user :content $ :content m :examples $ [] - |models-menu $ %{} :CodeEntry (:doc |) + |models-menu $ %{} :CodeEntry (:doc |) (:schema nil) :code $ quote def models-menu $ [] (:: :item :gemini-flash "|Gemini Flash 3") (:: :item :gemini-pro "|Gemini Pro 3.1") (:: :item :gemini-3.1-flash-lite-preview "|Gemini Flash Lite 3.1") (:: :item :flash-imagen "\"Flash Imagen") (:: :item :imagen-4 "\"Imagen 4") (:: :item :gemma "|Gemma 3 27b") (:: :item :openrouter/anthropic/claude-sonnet-4.5 "\"Openrouter Claude Sonnet 4.5") (:: :item :openrouter/anthropic/claude-opus-4 "\"Openrouter Claude Opus 4") (:: :item :openrouter/google/gemini-2.5-pro-preview "\"Openrouter Google Gemini 2.5 pro preview") (:: :item :openrouter/google/gemini-2.5-flash-preview-05-20 "\"Openrouter Google Gemini 2.5 flash preview") (:: :item :openrouter/openai/gpt-5 "\"Openrouter GPT 5") (:: :item :openrouter/deepseek/deepseek-chat-v3.1 "\"Openrouter deepseek-chat-v3.1") (; :: :item :claude-4.5 "\"Claude 4.5") :examples $ [] - |on-fill $ %{} :CodeEntry (:doc |) + |on-fill $ %{} :CodeEntry (:doc |) (:schema nil) :code $ quote defn on-fill (cursor state on-submit) %{} respo.schema/RespoListener (:name :on-fill) @@ -812,16 +936,16 @@ on-submit (:text info) (:search? state) (:think? state) dispatch! , nil :examples $ [] - |pattern-spaced-code $ %{} :CodeEntry (:doc |) + |pattern-spaced-code $ %{} :CodeEntry (:doc |) (:schema nil) :code $ quote def pattern-spaced-code $ noted "\"temp fix of nested code block" (&raw-code "\"/\\n\\s+```/g") :examples $ [] - |pick-model $ %{} :CodeEntry (:doc |) + |pick-model $ %{} :CodeEntry (:doc |) (:schema nil) :code $ quote defn pick-model (variant) case-default variant "\"gemini-3-flash-preview" (:gemini-3.1-flash-lite-preview "\"gemini-3.1-flash-lite-preview") (:gemini-pro "\"gemini-3.1-pro-preview") (:gemma "\"gemma-3-27b-it") :examples $ [] - |save-current-session $ %{} :CodeEntry (:doc |) + |save-current-session $ %{} :CodeEntry (:doc |) (:schema nil) :code $ quote defn save-current-session (store state) let @@ -836,18 +960,18 @@ assoc store :sessions $ append sessions updated-session , store :examples $ [] - |style-a-toggler $ %{} :CodeEntry (:doc |) + |style-a-toggler $ %{} :CodeEntry (:doc |) (:schema nil) :code $ quote defstyle style-a-toggler $ {} "\"&" $ {} (:cursor :pointer) (:background-color :white) (:color :black) "\".focus-within &" $ {} (:color :black) :examples $ [] - |style-abort-close $ %{} :CodeEntry (:doc |) + |style-abort-close $ %{} :CodeEntry (:doc |) (:schema nil) :code $ quote defstyle style-abort-close $ {} "\"&" $ {} (:vertical-align :middle) (:font-size 10) :examples $ [] - |style-app-global $ %{} :CodeEntry (:doc |) + |style-app-global $ %{} :CodeEntry (:doc |) (:schema nil) :code $ quote defstyle style-app-global $ {} str "\"& ." style-code-block @@ -858,22 +982,22 @@ "\"&:hover" $ {} (:color "\"#777") :background-color $ hsl 0 0 100 :examples $ [] - |style-checkbox $ %{} :CodeEntry (:doc |) + |style-checkbox $ %{} :CodeEntry (:doc |) (:schema nil) :code $ quote defstyle style-checkbox $ {} "\"&" $ {} (:cursor :pointer) (:user-select :none) (:font-size 12) (:line-height "\"28px") (:vertical-align :middle) :examples $ [] - |style-clear $ %{} :CodeEntry (:doc |) + |style-clear $ %{} :CodeEntry (:doc |) (:schema nil) :code $ quote defstyle style-clear $ {} "\"&" $ {} (:opacity 0.4) (:padding "\"4px 8px") (:display :inline-block) (:height "\"24px") :examples $ [] - |style-code-content $ %{} :CodeEntry (:doc |) + |style-code-content $ %{} :CodeEntry (:doc |) (:schema nil) :code $ quote defstyle style-code-content $ {} "\"&" $ {} (:line-height "\"1.5") (:font-size 13) :examples $ [] - |style-delete-button $ %{} :CodeEntry (:doc |) + |style-delete-button $ %{} :CodeEntry (:doc |) (:schema nil) :code $ quote defstyle style-delete-button $ {} |& $ {} (:padding "|4px 8px") (:font-size |18px) (:font-weight |50) @@ -887,7 +1011,7 @@ |&:active $ {} (:opacity 1) :color $ hsl 0 90 40 :examples $ [] - |style-fill $ %{} :CodeEntry (:doc |) + |style-fill $ %{} :CodeEntry (:doc |) (:schema nil) :code $ quote defstyle style-fill $ {} "\"&" $ {} (:cursor :pointer) (:user-select :none) (:display :inline-flex) (:align-items :center) (:justify-content :center) (:transition-duration "\"200ms") @@ -897,12 +1021,26 @@ :color $ hsl 0 0 40 :transform "\"scale(1.06)" :examples $ [] - |style-gap12 $ %{} :CodeEntry (:doc |) + |style-focus-box $ %{} :CodeEntry (:doc |) (:schema nil) + :code $ quote + defstyle style-focus-box $ {} + "\"&" $ {} (:width |100%) (:border-radius 12) (:min-height 40) (:max-height 40) (:padding "\"9px 12px") (:cursor :text) (:overflow :hidden) (:white-space :pre) (:text-overflow :ellipsis) (:background-color :transparent) + :examples $ [] + |style-focus-link $ %{} :CodeEntry (:doc |) (:schema nil) + :code $ quote + defstyle style-focus-link $ {} + "\"&" $ {} (:cursor :pointer) (:font-size 13) + :color $ hsl 200 80 40 + :text-decoration :none + :padding "\"4px 0" + "\"&:hover" $ {} (:text-decoration :underline) + :examples $ [] + |style-gap12 $ %{} :CodeEntry (:doc |) (:schema nil) :code $ quote defstyle style-gap12 $ {} "\"&" $ {} (:gap 12) :examples $ [] - |style-history-button $ %{} :CodeEntry (:doc |) + |style-history-button $ %{} :CodeEntry (:doc |) (:schema nil) :code $ quote defstyle style-history-button $ {} |& $ {} (:font-size |20px) @@ -915,7 +1053,7 @@ |&:hover $ {} :color $ hsl 200 80 50 :examples $ [] - |style-history-count $ %{} :CodeEntry (:doc |) + |style-history-count $ %{} :CodeEntry (:doc |) (:schema nil) :code $ quote defstyle style-history-count $ {} |& $ {} @@ -923,39 +1061,39 @@ :font-size |12px :display :inline-block :examples $ [] - |style-image $ %{} :CodeEntry (:doc |) + |style-image $ %{} :CodeEntry (:doc |) (:schema nil) :code $ quote defstyle style-image $ {} "\"&" $ {} (:max-width "\"100%") (:align-self :flex-start) (:border-radius "\"6px") :border $ str "\"1px solid " (hsl 0 0 90) :examples $ [] - |style-md-content $ %{} :CodeEntry (:doc |) + |style-md-content $ %{} :CodeEntry (:doc |) (:schema nil) :code $ quote defstyle style-md-content $ {} "\"& .md-p" $ {} (:margin "\"16px 0") (:line-height "\"1.6") :examples $ [] - |style-message-actions $ %{} :CodeEntry (:doc |) + |style-message-actions $ %{} :CodeEntry (:doc |) (:schema nil) :code $ quote defstyle style-message-actions $ {} "\"&" $ {} (:margin-top 6) (:justify-content :flex-end) (:width "\"100%") :examples $ [] - |style-message-area $ %{} :CodeEntry (:doc |) + |style-message-area $ %{} :CodeEntry (:doc |) (:schema nil) :code $ quote defstyle style-message-area $ {} "\"&" $ {} (:flex 2) (:overflow :scroll) :examples $ [] - |style-message-assistant $ %{} :CodeEntry (:doc |) + |style-message-assistant $ %{} :CodeEntry (:doc |) (:schema nil) :code $ quote defstyle style-message-assistant $ {} "\"&" $ {} (:align-self :flex-start) :examples $ [] - |style-message-box $ %{} :CodeEntry (:doc |) + |style-message-box $ %{} :CodeEntry (:doc |) (:schema nil) :code $ quote defstyle style-message-box $ {} "\"&" $ {} (:width "\"100%") (:max-width 1200) (:right "\"50%") (:padding "\"8px") (:margin :auto) (:transition-duration "\"300ms") (; :transform "\"translate(50%,0)") (:transition-property "\"height") "\"&:focus-within" $ {} (:opacity 1) (; :transform "\"translate(50%,0)") :examples $ [] - |style-message-box-panel $ %{} :CodeEntry (:doc |) + |style-message-box-panel $ %{} :CodeEntry (:doc |) (:schema nil) :code $ quote defstyle style-message-box-panel $ {} "\"&" $ {} (:position :absolute) (:bottom 0) (:opacity 1) (:width "\"100%") @@ -965,17 +1103,17 @@ :background-color $ hsl 0 0 100 0.9 :box-shadow $ str "\"0 0px 8px " (hsl 0 0 0 0.3) :examples $ [] - |style-message-item $ %{} :CodeEntry (:doc |) + |style-message-item $ %{} :CodeEntry (:doc |) (:schema nil) :code $ quote defstyle style-message-item $ {} "\"&" $ {} (:line-height "\"1.6") :examples $ [] - |style-message-list $ %{} :CodeEntry (:doc |) + |style-message-list $ %{} :CodeEntry (:doc |) (:schema nil) :code $ quote defstyle style-message-list $ {} "\"&" $ {} (:flex 2) (:padding "\"40px 16px 20vh 16px") (:width "\"100%") (:max-width 1200) (:margin :auto) (:position :relative) :examples $ [] - |style-message-role $ %{} :CodeEntry (:doc |) + |style-message-role $ %{} :CodeEntry (:doc |) (:schema nil) :code $ quote defstyle style-message-role $ {} "\"&" $ {} (:font-size 12) @@ -983,12 +1121,12 @@ :margin-bottom 6 :padding-right "\"16px" :examples $ [] - |style-message-text $ %{} :CodeEntry (:doc |) + |style-message-text $ %{} :CodeEntry (:doc |) (:schema nil) :code $ quote defstyle style-message-text $ {} "\"&" $ {} (:white-space :pre-wrap) (:line-height "\"1.6") (:margin 0) (:padding-right "\"16px") :examples $ [] - |style-message-user $ %{} :CodeEntry (:doc |) + |style-message-user $ %{} :CodeEntry (:doc |) (:schema nil) :code $ quote defstyle style-message-user $ {} "\"&" $ {} (:align-self :flex-end) @@ -1004,7 +1142,7 @@ :border-radius "\"2px" "\"&::-webkit-scrollbar-track" $ {} (:background-color :transparent) :examples $ [] - |style-more $ %{} :CodeEntry (:doc |) + |style-more $ %{} :CodeEntry (:doc |) (:schema nil) :code $ quote defstyle style-more $ {} "\"&" $ {} (:text-align :center) (:min-width 80) @@ -1017,12 +1155,12 @@ "\"&:hover" $ {} :box-shadow $ str "\"1px 1px 4px " (hsl 0 0 0 0.2) :examples $ [] - |style-reply-actions $ %{} :CodeEntry (:doc |) + |style-reply-actions $ %{} :CodeEntry (:doc |) (:schema nil) :code $ quote defstyle style-reply-actions $ {} "\"&" $ {} (:margin-top 6) (:justify-content :flex-start) (:width "\"100%") :examples $ [] - |style-reply-button $ %{} :CodeEntry (:doc |) + |style-reply-button $ %{} :CodeEntry (:doc |) (:schema nil) :code $ quote defstyle style-reply-button $ {} "\"&" $ {} (:text-align :center) (:min-width 80) @@ -1035,7 +1173,7 @@ "\"&:hover" $ {} :box-shadow $ str "\"1px 1px 4px " (hsl 0 0 0 0.2) :examples $ [] - |style-session-item $ %{} :CodeEntry (:doc |) + |style-session-item $ %{} :CodeEntry (:doc |) (:schema nil) :code $ quote defstyle style-session-item $ {} |& $ {} (:padding |12px) @@ -1047,23 +1185,29 @@ |:hover $ {} :background-color $ hsl 0 0 96 :examples $ [] - |style-sessions-list $ %{} :CodeEntry (:doc |) + |style-sessions-list $ %{} :CodeEntry (:doc |) (:schema nil) :code $ quote defstyle style-sessions-list $ {} |& $ {} (:flex |1) (:overflow-y :auto) (:min-width |300px) :examples $ [] - |style-submit $ %{} :CodeEntry (:doc |) + |style-submit $ %{} :CodeEntry (:doc |) (:schema nil) :code $ quote defstyle style-submit $ {} "\"&" $ {} :examples $ [] - |style-textbox $ %{} :CodeEntry (:doc |) + |style-textbox $ %{} :CodeEntry (:doc |) (:schema nil) :code $ quote defstyle style-textbox $ {} "\"&" $ {} (:border-radius 12) (:height "|max(100px,15vh)") (:width "\"100%") (:transition-duration "\"320ms") (:border :none) (:background-color :transparent) "\"&.focus-within" $ {} (:height "|max(240px,32vh)") (:border :none) (:box-shadow :none) :examples $ [] - |style-thinking $ %{} :CodeEntry (:doc |) + |style-textbox-compact $ %{} :CodeEntry (:doc |) (:schema nil) + :code $ quote + defstyle style-textbox-compact $ {} + "\"&" $ {} (:height 40) (:min-height 40) (:max-height 40) (:overflow :hidden) + "\"&.focus-within" $ {} (:height "|max(240px,32vh)") (:min-height "\"unset") (:max-height "\"unset") + :examples $ [] + |style-thinking $ %{} :CodeEntry (:doc |) (:schema nil) :code $ quote defstyle style-thinking $ {} "\"&" $ {} (:max-height 200) (:overflow :auto) (:padding "\"12px 16px") @@ -1076,9 +1220,10 @@ :border $ str "\"1px solid " (hsl 0 0 90) "\"& .md-p" $ {} (:margin "\"4px 0") :examples $ [] - |submit-message! $ %{} :CodeEntry (:doc |) + |submit-message! $ %{} :CodeEntry (:doc |) (:schema nil) :code $ quote - defn submit-message! (cursor state prompt-text search? think? model d!) (hint-fn async) + defn submit-message! (cursor state prompt-text search? think? model d!) + hint-fn $ {} (:async true) let state1 $ assoc state :messages append-user-message (:messages state) prompt-text @@ -1112,7 +1257,7 @@ d! cursor $ -> state (assoc :answer err-text) (assoc :loading? false) (assoc :done? true) assoc :messages $ upsert-assistant-message (:messages state) err-text nil :examples $ [] - |upsert-assistant-message $ %{} :CodeEntry (:doc |) + |upsert-assistant-message $ %{} :CodeEntry (:doc |) (:schema nil) :code $ quote defn upsert-assistant-message (messages content thinking) let @@ -1126,7 +1271,7 @@ -> last-msg (assoc :content content) (assoc :thinking thinking) conj messages0 $ {} (:role :assistant) (:content content) (:thinking thinking) :examples $ [] - :ns $ %{} :CodeEntry (:doc |) + :ns $ %{} :NsEntry (:doc |) :code $ quote ns app.comp.container $ :require (respo-ui.css :as css) respo.css :refer $ defstyle @@ -1142,35 +1287,32 @@ "\"../extension/get-selected" :refer $ get-selected memof.once :refer $ memof1-call memof1-call-by "\"../lib/image" :refer $ base64ToBlob - "\"openai" :default OpenAI feather.core :refer $ comp-i respo-alerts.core :refer $ [] use-modal-menu use-prompt use-drawer - genai.sdk :as sdk - :examples $ [] + respo-ui.util :refer $ tab-echo! |app.config $ %{} :FileEntry :defs $ {} - |chrome-extension? $ %{} :CodeEntry (:doc |) + |chrome-extension? $ %{} :CodeEntry (:doc |) (:schema nil) :code $ quote def chrome-extension? $ and (some? js/window.chrome) (some? js/window.chrome.runtime) (some? js/window.chrome.runtime.id) :examples $ [] - |dev? $ %{} :CodeEntry (:doc |) + |dev? $ %{} :CodeEntry (:doc |) (:schema nil) :code $ quote def dev? $ = "\"dev" (get-env "\"mode" "\"release") :examples $ [] - |site $ %{} :CodeEntry (:doc |) + |site $ %{} :CodeEntry (:doc |) (:schema nil) :code $ quote def site $ {} (:storage-key "\"msg-buffer") :examples $ [] - :ns $ %{} :CodeEntry (:doc |) + :ns $ %{} :NsEntry (:doc |) :code $ quote (ns app.config) - :examples $ [] |app.main $ %{} :FileEntry :defs $ {} - |*reel $ %{} :CodeEntry (:doc |) + |*reel $ %{} :CodeEntry (:doc |) (:schema nil) :code $ quote defatom *reel $ -> reel-schema/reel (assoc :base schema/store) (assoc :store schema/store) :examples $ [] - |connect-to-worker! $ %{} :CodeEntry (:doc |) + |connect-to-worker! $ %{} :CodeEntry (:doc |) (:schema nil) :code $ quote defn connect-to-worker! () $ if and (some? js/window.chrome) (some? js/window.chrome.runtime) (some? js/window.chrome.runtime.connect) @@ -1183,7 +1325,7 @@ do (println "|Worker disconnected, retrying in 500ms...") (js/setTimeout connect-to-worker! 500) , nil :examples $ [] - |dispatch! $ %{} :CodeEntry (:doc |) + |dispatch! $ %{} :CodeEntry (:doc |) (:schema nil) :code $ quote defn dispatch! (op) when @@ -1191,7 +1333,20 @@ js/console.log "\"Dispatch:" op reset! *reel $ reel-updater updater @*reel op :examples $ [] - |listen-extension! $ %{} :CodeEntry (:doc |) + |hydrate-storage-later! $ %{} :CodeEntry (:doc |) (:schema nil) + :code $ quote + defn hydrate-storage-later! () $ js/setTimeout + fn () $ let + raw $ js/localStorage.getItem (:storage-key config/site) + when (some? raw) + let + t_start $ .!now js/Date + dispatch! $ :: :hydrate-storage (parse-cirru-edn raw) + println "\"Hydrated in" + - (.!now js/Date) t_start + , "\"ms" + :examples $ [] + |listen-extension! $ %{} :CodeEntry (:doc |) (:schema nil) :code $ quote defn listen-extension! () js/chrome.runtime.onMessage.addListener $ fn (message sender respond!) @@ -1226,9 +1381,11 @@ send-to-component! event-tuple connect-to-worker! :examples $ [] - |main! $ %{} :CodeEntry (:doc |) + |main! $ %{} :CodeEntry (:doc |) (:schema nil) :code $ quote - defn main! () + defn main! () $ let + t0 $ .!now js/Date + println "\"Starting main! at" t0 println "\"Running mode:" $ if config/dev? "\"dev" "\"release" if config/dev? $ load-console-formatter! render-app! @@ -1242,26 +1399,24 @@ fn (event) if (.-ctrlKey event) (.!preventDefault event) js-object $ :passive false - ; flipped js/setInterval 60000 persist-storage! - let - raw $ js/localStorage.getItem (:storage-key config/site) - when (some? raw) - dispatch! $ :: :hydrate-storage (parse-cirru-edn raw) + hydrate-storage-later! if config/chrome-extension? $ listen-extension! - println "|App started." + let + t1 $ .!now js/Date + println "|App started at" t1 |cost (- t1 t0) "\"ms" :examples $ [] - |mount-target $ %{} :CodeEntry (:doc |) + |mount-target $ %{} :CodeEntry (:doc |) (:schema nil) :code $ quote def mount-target $ js/document.querySelector |.app :examples $ [] - |persist-storage! $ %{} :CodeEntry (:doc |) + |persist-storage! $ %{} :CodeEntry (:doc |) (:schema nil) :code $ quote defn persist-storage! () println "\"Saved at" $ .!toISOString (new js/Date) js/localStorage.setItem (:storage-key config/site) format-cirru-edn $ :store @*reel :examples $ [] - |reload! $ %{} :CodeEntry (:doc |) + |reload! $ %{} :CodeEntry (:doc |) (:schema nil) :code $ quote defn reload! () $ if (nil? build-errors) do (remove-watch *reel :changes) (clear-cache!) @@ -1270,11 +1425,19 @@ hud! "\"ok~" "\"Ok" hud! "\"error" build-errors :examples $ [] - |render-app! $ %{} :CodeEntry (:doc |) + |render-app! $ %{} :CodeEntry (:doc |) (:schema nil) :code $ quote - defn render-app! () $ render! mount-target (comp-container @*reel) dispatch! - :examples $ [] - :ns $ %{} :CodeEntry (:doc |) + defn render-app! () + let + t_start $ .!now js/Date + println "\"Rendering app..." + render! mount-target (comp-container @*reel) dispatch! + println "\"Rendered in" + - (.!now js/Date) t_start + , "\"ms" + render! mount-target (comp-container @*reel) dispatch! + :examples $ [] + :ns $ %{} :NsEntry (:doc |) :code $ quote ns app.main $ :require respo.core :refer $ render! clear-cache! @@ -1288,10 +1451,9 @@ "\"./calcit.build-errors" :default build-errors "\"bottom-tip" :default hud! respo.controller.client :refer $ send-to-component! - :examples $ [] |app.schema $ %{} :FileEntry :defs $ {} - |store $ %{} :CodeEntry (:doc |) + |store $ %{} :CodeEntry (:doc |) (:schema nil) :code $ quote def store $ {} :states $ {} @@ -1300,12 +1462,11 @@ :current-session-id nil :model nil :examples $ [] - :ns $ %{} :CodeEntry (:doc |) + :ns $ %{} :NsEntry (:doc |) :code $ quote (ns app.schema) - :examples $ [] |app.updater $ %{} :FileEntry :defs $ {} - |updater $ %{} :CodeEntry (:doc |) + |updater $ %{} :CodeEntry (:doc |) (:schema nil) :code $ quote defn updater (store op op-id op-time) tag-match op @@ -1331,11 +1492,14 @@ or (:sessions store) ([]) fn (s) not $ = (:id s) id + (:clear-sessions) + -> store + assoc :sessions $ [] + assoc :current-session-id nil _ $ do (eprintln "\"unknown op:" op) store :examples $ [] - :ns $ %{} :CodeEntry (:doc |) + :ns $ %{} :NsEntry (:doc |) :code $ quote ns app.updater $ :require respo.cursor :refer $ update-states update-states-merge app.comp.container :refer $ save-current-session generate-session-id - :examples $ [] diff --git a/deps.cirru b/deps.cirru index e5b3ea9..f5f8751 100644 --- a/deps.cirru +++ b/deps.cirru @@ -1,11 +1,10 @@ -{} (:calcit-version |0.11.8) +{} (:calcit-version |0.12.11) :dependencies $ {} (|Memkits/genai.calcit |0.0.2) |Respo/alerts.calcit |0.10.9 |Respo/reel.calcit |main |Respo/respo-feather.calcit |main |Respo/respo-markdown.calcit |0.4.11 |Respo/respo-ui.calcit |0.6.4 - |Respo/respo.calcit |0.16.28 - |calcit-lang/lilac |main + |Respo/respo.calcit |0.16.32 |calcit-lang/memof |0.0.23 diff --git a/index.html b/index.html index eca3b2b..063b0d6 100644 --- a/index.html +++ b/index.html @@ -1,19 +1,26 @@ - - + +
+' [--dep ...]` - 执行一段 Calcit 代码片段,用于快速验证写法
- - `--dep` 参数可以加载 `~/.config/calcit/modules/` 中的模块(直接使用模块名)
- - 示例:`cr eval 'echo 1' --dep calcit.std`
- - 可多次使用 `--dep` 加载多个模块
-
-### 查询子命令 (`cr query`)
-
-这些命令用于查询项目信息:
-
-**项目全局分析:**
-
-- `cr analyze call-graph` - 分析从入口点开始的调用图结构
-- `cr analyze count-calls` - 统计每个定义的调用次数
-
- _使用示例:_
-
- ```bash
- # 分析整个项目的调用图
- cr analyze call-graph
- # 统计调用次数
- cr analyze count-calls
- ```
-
-**基础查询:**
-
-- `cr query ns [--deps]` - 列出项目中所有命名空间(--deps 包含依赖)
-- `cr query ns ` - 读取命名空间详情(imports, 定义预览)
-- `cr query defs ` - 列出命名空间中的定义
-- `cr query pkg` - 获取项目包名
-- `cr query config` - 读取项目配置(init_fn, reload_fn, version)
-- `cr query error` - 读取 .calcit-error.cirru 错误堆栈文件
-- `cr query modules` - 列出项目依赖的模块(来自 compact.cirru 配置)
-
-**渐进式代码探索(Progressive Disclosure):**
-
-- `cr query peek ` - 查看定义签名(参数、文档、表达式数量),不返回完整实现体
- - 输出:Doc、Form 类型、参数列表、Body 表达式数量、首个表达式预览、Examples 数量
- - 用于快速了解函数接口,减少 token 消耗
-- `cr query def [-j]` - 读取定义的完整 Cirru 代码
- - 默认输出:Doc、Examples 数量、Cirru 格式代码
- - `-j` / `--json`:同时输出 JSON 格式(用于程序化处理)
- - 推荐:LLM 直接读取 Cirru 格式即可,通常不需要 JSON
-- `cr query examples ` - 读取定义的示例代码
- - 输出:每个 example 的 Cirru 格式和 JSON 格式
-
-**符号搜索与引用分析:**
-
-- `cr query find [--deps] [-f] [-n ]` - 跨命名空间搜索符号
- - 默认精确匹配:返回定义位置 + 所有引用位置(带上下文预览)
- - `-f` / `--fuzzy`:模糊搜索,匹配 "namespace/definition" 格式的路径
- - `-n `:限制模糊搜索结果数量(默认 20)
- - `--deps`:包含核心命名空间(calcit.\* 开头)
-- `cr query usages [--deps]` - 查找定义的所有使用位置
- - 返回:引用该定义的所有位置(带上下文预览)
- - 用于理解代码影响范围,重构前的影响分析
-
-**代码模式搜索(快速定位 ⭐⭐⭐):**
-
-- `cr query search [-f ] [-l]` - 搜索叶子节点(符号/字符串),比逐层导航快 10 倍
- - `-f ` - 过滤到特定命名空间或定义
- - `-l / --loose`:宽松匹配,包含模式
- - `-d `:限制搜索深度
- - `-p `:从指定路径开始搜索(如 `"3,2,1"`)
- - 返回:完整路径 + 父级上下文,多个匹配时自动显示批量替换命令
- - 示例:
- - `cr query search 'println' -f app.main/main!` - 精确搜索
- - `cr query search 'comp-' -f app.ui/layout -l` - 模糊搜索(所有 comp- 开头)
- - `cr query search 'task-id' -f app.comp/render` - 返回所有匹配位置并自动排序
-
-**高级结构搜索(搜索代码结构 ⭐⭐⭐):**
-
-- `cr query search-expr [-f ] [-l] [-j]` - 搜索结构表达式(List)
- - `-l / --loose`:宽松匹配,查找包含连续子序列的结构
- - `-j / --json`:将模式解析为 JSON 数组
- - 示例:
- - `cr query search-expr 'fn (x)' -f app.main/process -l` - 查找函数定义
- - `cr query search-expr '>> state task-id'` - 查找状态访问
- - `cr query search-expr 'memof1-call-by' -l` - 查找记忆化调用
-
-**搜索结果格式:** `[索引1,索引2,...] in 父级上下文`,可配合 `cr tree show -p ''` 查看节点。**修改代码时优先用 search 命令,比逐层导航快 10 倍。**
-
-### LLM 辅助:动态方法提示
-
-- `&inspect-class-methods` - 打印某个值对应 class 的方法清单(不改变原值)
- - 用法:`(&inspect-class-methods value |optional note)`
- - 用途:帮助 LLM 发现动态类型的方法与 proc 签名信息(不是测试/验证用途)
- - 适合在 pipeline 中插入,快速查看方法、参数、命名空间和 proc 类型信息
-
-### 文档子命令 (`cr docs`)
-
-查询 Calcit 语言文档(guidebook):
-
-- `cr docs search [-c ] [-f ]` - 按关键词搜索文档内容
- - `-c ` - 显示匹配行的上下文行数(默认 5)
- - `-f ` - 按文件名过滤搜索结果
- - 输出:匹配行及其上下文,带行号和高亮
- - 示例:`cr docs search "macro" -c 10` 或 `cr docs search "defn" -f macros.md`
-
-- `cr docs read [-s ] [-n ]` - 阅读指定文档
- - `-s ` - 起始行号(默认 0)
- - `-n ` - 读取行数(默认 80)
- - 输出:文档内容、当前范围、是否有更多内容
- - 示例:`cr docs read macros.md` 或 `cr docs read intro.md -s 20 -n 30`
-
-- `cr docs list` - 列出所有可用文档
-
-### Cirru 语法工具 (`cr cirru`)
-
-用于 Cirru 语法和 JSON 之间的转换:
-
-- `cr cirru parse ''` - 解析 Cirru 代码为 JSON
-- `cr cirru format ''` - 格式化 JSON 为 Cirru 代码
-- `cr cirru parse-edn ''` - 解析 Cirru EDN 为 JSON
-- `cr cirru show-guide` - 显示 Cirru 语法指南(帮助 LLM 生成正确的 Cirru 代码)
-
-**⚠️ 重要:生成 Cirru 代码前请先阅读语法指南**
-
-运行 `cr cirru show-guide` 获取完整的 Cirru 语法说明,包括:
-
-- `$` 操作符(单节点展开)
-- `|` 前缀(字符串字面量), 这个是 Cirru 特殊的地方, 而不是直接用引号包裹
-- `,` 操作符(注释标记)
-- `~` 和 `~@`(宏展开)
-- 常见错误和避免方法
-
-### 库管理 (`cr libs`)
-
-查询和了解 Calcit 官方库:
-
-- `cr libs` - 列出所有官方库
-- `cr libs search ` - 按关键词搜索库(搜索名称、描述、分类)
-- `cr libs readme [-f ]` - 查看库的文档
- - 优先从本地 `~/.config/calcit/modules/` 读取
- - 本地不存在时从 GitHub 仓库获取
- - `-f` 参数可指定其他文档文件(如 `-f Skills.md`)
- - 默认读取 `README.md`
-- `cr libs scan-md ` - 扫描本地模块目录下的所有 `.md` 文件
- - 递归扫描子目录
- - 显示相对路径列表
-- `caps` - 安装/更新依赖
-
-**查看已安装模块:**
-
-```bash
-# 列出 ~/.config/calcit/modules/ 下所有已安装的模块
-ls ~/.config/calcit/modules/
-
-# 查看当前项目配置的模块依赖
-cr query modules
-```
-
-### 精细代码树操作 (`cr tree`)
-
-⚠️ **关键警告:路径索引动态变化**
-
-删除或插入节点后,同级后续节点的索引会自动改变。**必须从后往前操作**或**每次修改后重新搜索路径**。
-
-**核心概念:**
-
-- 路径格式:逗号分隔的索引(如 `"3,2,1"`),空字符串 `""` 表示根节点
-- 每个命令都有 `--help` 查看详细参数
-- 命令执行后会显示 "Next steps" 提示下一步操作
-
-**主要操作:**
-
-- `cr tree show -p '' [-j]` - 查看节点
- - 默认输出:节点类型、Cirru 预览、子节点索引列表、操作提示
- - `-j` / `--json`:同时输出 JSON 格式(用于程序化处理)
- - 推荐:直接查看 Cirru 格式即可,通常不需要 JSON
-- `cr tree replace` - 替换节点
-- `cr tree replace-leaf` - 查找并替换所有匹配的 leaf 节点(无需指定路径)
- - `--pattern ` - 要搜索的模式(精确匹配 leaf 节点)
- - `--replacement ` - 替换值(默认作为 leaf 节点处理)
- - 自动遍历整个定义,一次性替换所有匹配项
- - 示例:`cr tree replace-leaf 'ns/def' --pattern 'old-name' --replacement 'new-name'`
-- `cr tree delete` - 删除节点
-- `cr tree insert-before/after` - 插入相邻节点
-- `cr tree insert-child/append-child` - 插入子节点
-- `cr tree swap-next/prev` - 交换相邻节点
-- `cr tree wrap` - 用新结构包装节点
-
-**输入方式(通用):**
-
-- `-e ''` - 内联代码(自动识别 Cirru/JSON)
-- `--leaf` - 强制作为 leaf 节点(符号或字符串)
-- `-j ''` / `-f ` / `-s` (stdin)
-
-**推荐工作流(高效定位 ⭐⭐⭐):**
-
-```bash
-# ===== 方案 A:单点修改(精确定位) =====
-
-# 1. 快速定位目标节点(一步到位)
-cr query search "target-symbol" -f namespace/def
-# 输出:[3,2,5,1] in (fn (x) target-symbol ...)
-
-# 2. 直接修改(路径已知)
-cr tree replace namespace/def -p '3,2,5,1' --leaf -e 'new-symbol'
-
-# 3. 验证结果(可选)
-cr tree show namespace/def -p '3,2,5,1'
-
-
-# ===== 方案 B:批量重命名(多处修改) =====
-
-# 1. 搜索所有匹配位置
-cr query search "old-name" -f namespace/def
-# 自动显示:4 处匹配,已按路径从大到小排序
-# [3,2,5,8] [3,2,5,2] [3,1,0] [2,1]
-
-# 2. 按提示从后往前修改(避免路径变化)
-cr tree replace namespace/def -p '3,2,5,8' --leaf -e 'new-name'
-cr tree replace namespace/def -p '3,2,5,2' --leaf -e 'new-name'
-# ... 继续按序修改
-
-# 或:一次性替换所有匹配项
-cr tree replace-leaf namespace/def --pattern 'old-name' --replacement 'new-name'
-
-
-# ===== 方案 C:结构搜索(查找表达式) =====
-
-# 1. 搜索包含特定模式的表达式
-cr query search-expr "fn (task)" -f namespace/def -l
-# 输出:[3,2,2,5,2,4,1] in (map $ fn (task) ...)
-
-# 2. 查看完整结构(可选)
-cr tree show namespace/def -p '3,2,2,5,2,4,1'
-
-# 3. 修改整个表达式或子节点
-cr tree replace namespace/def -p '3,2,2,5,2,4,1,2' -e 'let ((x 1)) (+ x task)'
-```
-
-**关键技巧:**
-
-- **优先使用 `search` 系列命令**:比逐层导航快 10+ 倍,一步直达目标
-- **路径格式**:`"3,2,1"` 表示第3个子节点 → 第2个子节点 → 第1个子节点
-- **批量修改自动提示**:搜索找到多处时,自动显示路径排序和批量替换命令
-- **路径动态变化**:删除/插入后,同级后续索引会变化,按提示从后往前操作
-- 所有命令都会显示 Next steps 和操作提示
-
-**结构化变更示例:**
-
-这些高级操作允许你在修改时引用原始节点及其内部结构:
-
-- **包裹节点**(使用 `cr tree wrap` 或 `cr tree replace` 的 `--refer-original`):
-
- ```bash
- # 将路径 "3,2" 的节点包裹在 println 中
- cr tree wrap ns/def -p '3,2' -e 'println $$$$' --refer-original '$$$$'
- ```
-
-- **重构并复用原子节点**(使用 `--refer-inner-branch`):
- - 假设原节点是 `(+ 1 2)` (路径 "3,1"),其子节点索引 1 是 `1`,索引 2 是 `2`
- - 将其重构为 `(* 2 10)`:
-
- ```bash
- cr tree replace ns/def -p '3,1' -e '(* #### 10)' --refer-inner-branch '2' --refer-inner-placeholder '####'
- ```
-
-- **多处重用原始节点**:
- ```bash
- # 将节点 x 变为 (+ x x)
- cr tree replace ns/def -p '2' -e '(+ $ $)' --refer-original '$'
- ```
- 详细参数和示例使用 `cr tree --help` 查看。
-
-### 代码编辑 (`cr edit`)
-
-直接编辑 compact.cirru 项目代码,支持三种输入方式:
-
-- `--file ` 或 `-f ` - 从文件读取(默认 Cirru 格式,使用 `-J` 指定 JSON)
-- `--json ` 或 `-j ` - 内联 JSON 字符串
-- `--stdin` 或 `-s` - 从标准输入读取(默认 Cirru 格式,使用 `-J` 指定 JSON)
-
-额外支持“内联代码”参数:
-
-- `--code ` 或 `-e `:直接在命令行里传入一段代码。
- - 默认按 **Cirru 单行表达式(one-liner)** 解析。
- - 如果输入“看起来像 JSON”(例如 `-e '"abc"'`,或 `-e '["a"]'` 这类 `[...]` 且包含 `"`),则会按 JSON 解析。
- - ⚠️ 当输入看起来像 JSON 但 JSON 不合法时,会直接报错(不会回退当成 Cirru one-liner)。
-
-对 `--file/--stdin` 输入,还支持以下“格式开关”(与 `-J/--json-input` 类似):
-
-- `--leaf`:把输入当成 **leaf 节点**,直接使用 Cirru 符号或 `|text` 字符串,无需 JSON 引号。
- - 传入符号:`-e 'my-symbol'`
- - 传入字符串:加 Cirru 字符串前缀 `|` 或 `"`,例如 `-e '|my string'` 或 `-e '"my string'`
-
-⚠️ 注意:这些开关彼此互斥(一次只用一个)。
-
-**推荐简化规则(命令行更好写):**
-
-- **JSON(单行)**:优先用 `-j ''` 或 `-e ''`(不需要 `-J`)。
-- **Cirru 单行表达式**:用 `-e ''`(`-e` 默认按 one-liner 解析)。
-- **Cirru 多行缩进**:用 `-f file.cirru` 或 `-s`(stdin)。
-- `-J/--json-input` 主要用于 **file/stdin** 读入 JSON(如 `-f code.json -J` 或 `-s -J`)。
-
-补充:`-e/--code` 只有在 `[...]` 内部包含 `"` 时才会自动按 JSON 解析(例如 `-e '["a"]'`)。
-像 `-e '[]'` / `-e '[ ]'` 会默认按 Cirru one-liner 处理;如果你需要“空 JSON 数组”,用显式 JSON:`-j '[]'`。
-
-如果你想在命令行里明确“这段就是 JSON”,请用 `-j ''`(`-J` 是给 file/stdin 用的)。
-
-**定义操作:**
-
-- `cr edit def ` - 添加新定义(若已存在会报错,需用 `cr tree replace` 修改)
-- `cr edit rm-def ` - 删除定义
-- `cr edit doc ''` - 更新定义的文档
-- `cr edit examples ` - 设置定义的示例代码(批量替换)
-- `cr edit add-example ` - 添加单个示例
-- `cr edit rm-example ` - 删除指定索引的示例(0-based)
-
-**命名空间操作:**
-
-- `cr edit add-ns ` - 添加命名空间
-- `cr edit rm-ns ` - 删除命名空间
-- `cr edit imports ` - 更新导入规则(全量替换)
-- `cr edit add-import ` - 添加单个 import 规则
-- `cr edit rm-import ` - 移除指定来源的 import 规则
-- `cr edit ns-doc ''` - 更新命名空间文档
-
-**模块和配置:**
-
-- `cr edit add-module ` - 添加模块依赖
-- `cr edit rm-module ` - 删除模块依赖
-- `cr edit config ` - 设置配置(key: init-fn, reload-fn, version)
-
-**增量变更导出:**
-
-- `cr edit inc` - 记录增量代码变更并导出到 `.compact-inc.cirru`,触发 watcher 热更新
- - `--added ` - 标记新增的定义
- - `--changed ` - 标记修改的定义
- - `--removed ` - 标记删除的定义
- - `--added-ns ` - 标记新增的命名空间
- - `--removed-ns ` - 标记删除的命名空间
- - `--ns-updated ` - 标记命名空间导入变更
- - 配合 watcher 使用实现热更新(详见"开发调试"章节)
-
-使用 `--help` 参数了解详细的输入方式和参数选项。
-
----
-
-## Calcit 语言基础
-
-### Cirru 语法核心概念
-
-**与其他 Lisp 的区别:**
-
-- **缩进语法**:用缩进代替括号(类似 Python/YAML),单行用空格分隔
-- **字符串前缀**:`|hello` 或 `"hello"` 表示字符串,`|` 前缀更简洁
-- **无方括号花括号**:只用圆括号概念(体现在 JSON 转换中),Cirru 文本层面无括号
-
-**常见混淆点:**
-
-❌ **错误理解:** Calcit 字符串是 `"x"` → JSON 是 `"\"x\""`
-✅ **正确理解:** Cirru `|x` → JSON `"x"`,Cirru `"x"` → JSON `"x"`
-
-**字符串 vs 符号的关键区分:**
-
-- `|Add` 或 `"Add` → **字符串**(用于显示文本、属性值等, 前缀形式区分字面量类型)
-- `Add` → **符号/变量名**(Calcit 会在作用域中查找)
-- 常见错误:受其他语言习惯影响,忘记加 `|` 前缀导致 `unknown symbol` 错误
-
-**CLI 使用提示:**
-
-- 替换包含空格的字符串:`--leaf -e '|text with spaces'` 或 `-j '"text"'`
-- 避免解析为列表:字符串字面量必须用 `--leaf` 或 `-j` 明确标记
-
-**示例对照:**
-
-| Cirru 代码 | JSON 等价 | JavaScript 等价 |
-| ---------------- | -------------------------------- | ------------------------ |
-| `\|hello` | `"hello"` | `"hello"` |
-| `"world"` | `"world"` | `"world"` |
-| `\|a b c` | `"a b c"` | `"a b c"` |
-| `fn (x) (+ x 1)` | `["fn", ["x"], ["+", "x", "1"]]` | `fn(x) { return x + 1 }` |
-
-### 数据结构:Tuple vs Vector
-
-Calcit 特有的两种序列类型:
-
-**Tuple (`::`)** - 不可变、用于模式匹配
-
-```cirru
-; 创建 tuple
-:: :event/type data
-
-; 模式匹配
-tag-match event
- (:event/click data) (handle-click data)
- (:event/input text) (handle-input text)
-```
-
-**Vector (`[]`)** - 可变、用于列表操作
-
-```cirru
-; 创建 vector
-[] item1 item2 item3
-
-; DOM 列表
-div {} $ []
- button {} |Click
- span {} |Text
-```
-
-**常见错误:**
-
-```cirru
-; ❌ 错误:用 vector 传事件
-send-event! $ [] :clipboard/read text
-; 报错:tag-match expected tuple
-
-; ✅ 正确:用 tuple
-send-event! $ :: :clipboard/read text
-```
-
-### 类型标注与检查
-
-Calcit 支持可选的类型标注,用于开发时的类型检查。
-
-#### 函数参数类型 (`assert-type`)
-
-```cirru
-defn calculate-total (items discount)
- assert-type items :list
- assert-type discount :number
- ; let 绑定中的变量会自动推断类型
- let
- sum $ foldl items 0 &+
- * sum $ - 1 discount
-```
-
-#### 函数返回类型 (`hint-fn`)
-
-```cirru
-defn add-numbers (a b)
- assert-type a :number
- assert-type b :number
- hint-fn $ return-type :number
- &+ a b
-```
-
-**常见类型:** `:number` `:string` `:bool` `:list` `:map` `:set` `:tuple` `:keyword` `:nil`
-
-**验证类型:** `cr --check-only` 或 `cr ir -1` 查看 IR 中的类型信息
-
-### 其他易错点
-
-比较容易犯的错误:
-
-- Calcit 中字符串通过前缀区分,`|` 和 `"` 开头表示字符串。`|x` 对应 JavaScript 字符串 `"x"`。产生 JSON 时注意不要重复包裹引号。
-- Calcit 采用 Cirru 缩进语法,可以理解成去掉跨行括号改用缩进的 Lisp 变种。用 `cr cirru parse` 和 `cr cirru format` 互相转化试验。
-- Calcit 跟 Clojure 在语义上比较像,但语法层面只用圆括号,不用方括号花括号。
-
----
-
-## 开发调试
-
-简单脚本用 `cr -1 ` 直接执行。编译 JavaScript 用 `cr -1 js` 执行一次编译。
-
-Calcit snapshot 文件中 config 有 `init-fn` 和 `reload-fn` 配置:
-
-- 初次启动调用 `init-fn`
-- 每次修改代码后调用 `reload-fn`
-
-**典型开发流程:**
-
-```bash
-# 1. 启动监听模式(用户自行使用)
-cr # 解释执行模式
-cr js # JS 编译模式
-
-# 2. 修改代码后触发增量更新(详见"增量触发更新"章节)
-cr edit inc --changed ns/def
-
-# 3. 一次性执行/编译(用于简单脚本)
-cr -1 # 执行一次
-cr -1 js # 编译一次
-```
-
-### 增量触发更新(推荐)⭐⭐⭐
-
-当使用监听模式(`cr` 或 `cr js`)开发时,推荐使用 `cr edit inc` 命令触发增量更新,而非全量重新编译/执行:
-
-**工作流程:**
-
-```bash
-# 【终端 1】启动 watcher(监听模式)
-cr # 或 cr js
-
-# 【终端 2】修改代码后触发增量更新
-# 修改定义
-cr edit def app.core/my-fn -e 'defn my-fn (x) (+ x 1)'
-
-# 触发增量更新
-cr edit inc --changed app.core/my-fn
-
-# 等待 ~300ms 后查看编译结果
-cr query error
-```
-
-**增量更新命令参数:**
-
-```bash
-# 新增定义
-cr edit inc --added namespace/definition
-
-# 修改定义
-cr edit inc --changed namespace/definition
-
-# 删除定义
-cr edit inc --removed namespace/definition
-
-# 新增命名空间
-cr edit inc --added-ns namespace
-
-# 删除命名空间
-cr edit inc --removed-ns namespace
-
-# 更新命名空间导入
-cr edit inc --ns-updated namespace
-
-# 组合使用(批量更新)
-cr edit inc \
- --changed app.core/add \
- --changed app.core/multiply \
- --removed app.core/old-fn
-```
-
-**查看编译结果:**
-
-```bash
-cr query error # 命令会显示详细的错误信息或成功状态
-```
-
-**何时使用全量操作:**
-
-```bash
-# 极少数情况:增量更新不符合预期时
-cr -1 js # 重新编译 JavaScript
-cr -1 # 重新执行程序
-
-# 或重启监听模式(Ctrl+C 停止后重启)
-cr # 或 cr js
-
-# CI/CD 或脚本验证(不启动 watcher)
-cr --check-only # 仅语法检查
-```
-
-**增量更新优势:** 快速反馈、精确控制变更范围、watcher 保持运行状态
-
----
-
-## 文档支持
-
-遇到疑问时使用:
-
-- `cr docs search ` - 搜索 Calcit 教程内容
-- `cr docs read ` - 阅读完整文档
-- `cr docs list` - 查看所有可用文档
-- `cr query ns ` - 查看命名空间说明和函数文档
-- `cr query peek ` - 快速查看定义签名
-- `cr query def ` - 读取完整语法树
-- `cr query examples ` - 查看示例代码
-- `cr query find ` - 跨命名空间搜索符号
-- `cr query usages ` - 查找定义的使用位置
-- `cr query search [-f ]` - 搜索叶子节点
-- `cr query search-expr [-f ]` - 搜索结构表达式
-- `cr query error` - 查看最近的错误堆栈
-
----
-
-## 代码修改示例
-
-**添加新函数:**
-
-````bash
-# Cirru one liner
-cr edit def app.core/multiply -e 'defn multiply (x y) (* x y)'
-# 基本操作:**
-
-```bash
-# 添加新函数(命令会提示 Next steps)
-cr edit def 'app.core/multiply' -e 'defn multiply (x y) (* x y)'
-
-# 替换整个定义(-p '' 表示根路径)
-cr tree replace 'app.core/multiply' -p '' -e 'defn multiply (x y z) (* x y z)'
-
-# 更新文档和示例
-cr edit doc 'app.core/multiply' '乘法函数,返回两个数的积'
-cr edit add-example 'app.core/multiply' -e 'multiply 5 6'
-````
-
-**修改定义工作流(命令会显示子节点索引和 Next steps):**
-
-```bash
-# 1. 搜索定位
-cr query search '' -f 'ns/def' -l
-
-# 2. 查看节点(输出会显示索引和操作提示)
-cr tree show 'ns/def' -p ''
-
-# 3. 执行替换(会显示 diff 和验证命令)
-cr tree replace 'ns/def' -p '' --leaf -e ''
-
-# 4. 检查结果
-cr query error
-# 添加命名空间
-cr edit add-ns app.util
-
-# 添加导入规则
-cr edit add-import app.main -e 'app.util :refer $ helper'
-
-# 移除导入规则
-cr edit rm-import app.main app.util
-
-# 更新项目配置
-cr edit config init-fn app.main/main!
-```
-
-**更新命名空间导入(全量替换):**
-
-```bash
-cr edit imports app.main -j '[["app.lib", ":as", "lib"], ["app.util", ":refer", ["helper"]]]'
-```
-
----
-
-## ⚠️ 常见陷阱和最佳实践
-
-### 1. 路径索引动态变化问题 ⭐⭐⭐
-
-**核心原则:** 删除/插入会改变同级后续节点索引。
-
-**批量修改策略:**
-
-- **从后往前操作**(推荐):先删大索引,再删小索引
-- **单次操作后重新搜索**:每次修改立即用 `cr query search` 更新路径
-- **整体重写**:用 `cr tree replace -p ""` 替换整个定义
-
-命令会在路径错误时提示最长有效路径和可用子节点。
-
-### 2. 输入格式参数使用速查 ⭐⭐⭐
-
-**参数混淆矩阵(已全面支持 `-e` 自动识别):**
-
-| 场景 | 示例用法 | 解析结果 | 说明 |
-| ------------------- | -------------------------------------- | ----------------------------- | --------------------------------- |
-| **表达式 (Cirru)** | `-e 'defn add (a b) (+ a b)'` | `["defn", "add", ...]` (List) | 默认按 Cirru one-liner 解析 |
-| **原子符号 (Leaf)** | `--leaf -e 'my-symbol'` | `"my-symbol"` (Leaf) | **推荐**,避免被包装成 list |
-| **字符串 (Leaf)** | `--leaf -e '\|hello world'` | `"hello world"` (Leaf) | 符号前缀 `\|` 表示字符串 |
-| **JSON 数组** | `-e '["+", "x", "1"]'` | `["+", "x", "1"]` (List) | **自动识别** (含 `[` 且有 `"`) |
-| **JSON 字符串** | `-e '"my leaf"'` | `"my leaf"` (Leaf) | **自动识别** (含引用的字符串) |
-| **内联 JSON** | `-j '["defn", ...]'` | `["defn", ...]` (List) | 显式按 JSON 解析,忽略 Cirru 规则 |
-| **外部文件** | `-f code.cirru` (或 `-f code.json -J`) | 根据文件内容解析 | `-J` 用于标记文件内是 JSON |
-
-**核心规则:**
-
-1. **智能识别模式**:`-e / --code` 现在会自动识别 JSON。如果你传入 `["a"]` 或 `"a"`,它会直接按 JSON 处理,无需再额外加 `-J` 或 `-j`。
-2. **强制 Leaf 模式**:如果你需要确保输入是一个叶子节点(符号或字符串),请在任何地方使用 `--leaf` 开关。它会将原始输入直接作为内容,不经过任何解析。
-3. **显式 JSON 模式**:如果你想明确告诉工具“这段就是 JSON”,优先用 `-j ''`。
-4. **统一性**:`cr tree` 和 `cr edit` 的所有子命令(replace, def, insert 等)现在共享完全相同的输入解析逻辑。
-
-**实战示例:**
-
-```bash
-# ✅ 替换表达式
-cr tree replace app.main/fn -p "2" -e 'println |hello'
-
-# ✅ 替换 leaf(推荐 --leaf)
-cr tree replace app.main/fn -p "2,0" --leaf -e 'new-symbol'
-
-# ✅ 替换字符串 leaf
-cr tree replace app.main/fn -p "2,1" --leaf -e '|new text'
-
-# ❌ 避免:用 -e 传单个 token(会变成 list)
-cr tree replace app.main/fn -p "2,0" -e 'symbol' # 结果:["symbol"]
-```
-
-### 3. Cirru 字符串和数据类型 ⭐⭐
-
-**Cirru 字符串前缀:**
-
-| Cirru 写法 | JSON 等价 | 使用场景 |
-| -------------- | -------------- | ------------ |
-| `\|hello` | `"hello"` | 推荐,简洁 |
-| `"hello"` | `"hello"` | 也可以 |
-| `\|a b c` | `"a b c"` | 包含空格 |
-| `\|[tag] text` | `"[tag] text"` | 包含特殊字符 |
-
-**Tuple vs Vector:**
-
-```cirru
-; ✅ Tuple - 用于事件、模式匹配
-:: :clipboard/read text
-
-; ✅ Vector - 用于 DOM 列表
-[] (button) (div)
-
-; ❌ 错误:用 vector 传事件
-send-to-component! $ [] :clipboard/read text
-; 报错:tag-match expected tuple
-
-; ✅ 正确:用 tuple
-send-to-component! $ :: :clipboard/read text
-```
-
-**记忆规则:**
-
-- **`::` (tuple)**: 事件、模式匹配、不可变数据结构
-- **`[]` (vector)**: DOM 元素列表、动态集合
-
-### 4. 推荐工作流程
-
-**基本流程(search 快速定位 ⭐⭐⭐):**
-
-```bash
-# 1. 快速定位(比逐层导航快10倍)
-cr query search 'target' -f 'ns/def' # 或 search-expr 'fn (x)' -l 搜索结构
-
-# 2. 执行修改(会显示 diff 和验证命令)
-cr tree replace 'ns/def' -p "" --leaf -e ''
-
-# 3. 增量更新(推荐)
-cr edit inc --changed ns/def
-# 等待 ~300ms 后检查
-cr query error
-```
-
-**新手提示:**
-
-- 不知道目标在哪?用 `search` 或 `search-expr` 快速找到所有匹配
-- 想了解代码结构?用 `tree show` 逐层探索
-- 需要批量重命名?搜索后按提示从大到小路径依次修改
-- 不确定修改是否正确?每步后用 `tree show` 验证
-
-### 5. Shell 特殊字符转义 ⭐⭐
-
-Calcit 函数名中的 `?`, `->`, `!` 等字符在 bash/zsh 中有特殊含义,需要用单引号包裹:
-
-```bash
-# ❌ 错误
-cr query def app.main/valid?
-cr eval '-> x (+ 1) (* 2)'
-
-# ✅ 正确
-cr query def 'app.main/valid?'
-cr eval 'thread-first x (+ 1) (* 2)' # 用 thread-first 代替 ->
-```
-
-**建议:** 命令行中优先使用英文名称(`thread-first` 而非 `->`),更清晰且无需转义。
-
----
-
-## 💡 Calcit vs Clojure 关键差异
-
-**语法层面:**
-
-- **只用圆括号**:Calcit 的 Cirru 语法不使用方括号 `[]` 和花括号 `{}`,统一用缩进表达结构
-- **函数前缀**:Calcit 用 `&` 区分内置函数(`&+`、`&str`)和用户定义函数
-
-**集合函数参数顺序(易错 ⭐⭐⭐):**
-
-- **Calcit**: 集合在**第一位** → `map data fn` 或 `-> data (map fn)`
-- **Clojure**: 函数在第一位 → `(map fn data)` 或 `(->> data (map fn))`
-- **症状**:`unknown data for foldl-shortcut` 报错
-- **原因**:误用 `->>` 或参数顺序错误
-
-**其他差异:**
-
-- **宏系统**:Calcit 更简洁,缺少 Clojure 的 reader macro(如 `#()`)
-- **数据类型**:Calcit 的 Tuple (`::`) 和 Vector (`[]`) 有特定用途(见"Cirru 字符串和数据类型")
-
----
-
-## 常见错误排查
-
-| 错误信息 | 原因 | 解决方法 |
-| ---------------------------- | ----------------------- | --------------------------------- |
-| `Path index X out of bounds` | 路径已过期 | 重新运行 `cr query search` |
-| `tag-match expected tuple` | 传入 vector 而非 tuple | 改用 `::` |
-| 字符串被拆分 | 没有用 `\|` 或 `"` 包裹 | 使用 `\|complete string` |
-| `unexpected format` | 语法错误 | 用 `cr cirru parse ''` 验证 |
-
-**调试命令:** `cr query error`(会显示详细的错误堆栈和提示)
-
----
-
-Also read `llms/Respo.md` for framework usage.
diff --git a/llms/Respo.md b/llms/Respo.md
deleted file mode 100644
index 244336a..0000000
--- a/llms/Respo.md
+++ /dev/null
@@ -1,918 +0,0 @@
-# Respo Development Guide for LLM Agents
-
-**🤖 This guide is specifically designed for LLM agents to develop, debug, and maintain Respo applications.**
-
-📚 **Related Documentation**:
-
-- [← Back to README](../README.md)
-- [Beginner Guide](./beginner-guide.md)
-- [CLI Tools Reference](../Agents.md)
-- [API Reference](./api.md)
-
----
-
-## Project Structure
-
-The Respo project is a virtual DOM library written in Calcit-js, containing:
-
-- **Main codebase**: `compact.cirru` (2314 lines) - serialized source code
-- **Compiled source**: `calcit.cirru` (13806 lines) - full AST representation
-- **Namespaces**: 33 total namespaces organized by functionality
-- **Version**: 0.16.21
-- **Dependencies**: memof (memoization), lilac (UI utilities), calcit-test (testing)
-
-### Core Namespace Organization
-
-**User-facing APIs** (what you typically use):
-
-- `respo.core` - Core APIs: defcomp, div, render!, clear-cache!, etc.
-- `respo.comp.space` - Utility component comp-space (=<)
-- `respo.comp.inspect` - Debugging component comp-inspect
-- `respo.render.html` - HTML generation: make-string, make-html
-
-**Application layer** (in example app):
-
-- `respo.app.core` - Main application logic (\*store, dispatch!, render-app!)
-- `respo.app.schema` - Data structures and schemas
-- `respo.app.updater` - State management and updates
-- `respo.app.comp.*` - Application components (container, task, todolist, wrap, zero)
-- `respo.app.style.widget` - Application styles
-
-**Rendering and internal** (low-level):
-
-- `respo.render.diff` - Find differences between virtual DOM trees
-- `respo.render.dom` - DOM element creation and manipulation
-- `respo.render.effect` - Component lifecycle effects
-- `respo.render.patch` - Apply DOM patches
-- `respo.controller.client` - Client-side state management (activate-instance!, patch-instance!, send-to-component!)
-- `respo.controller.resolve` - Event handling and resolution (build-deliver-event, wrap-dispatch)
-
-**Utilities**:
-
-- `respo.util.dom` - DOM utilities
-- `respo.util.format` - Element formatting (purify-element, mute-element)
-- `respo.util.list` - List utilities (map-val, map-with-idx)
-- `respo.util.detect` - Type detection (component?, element?, effect?)
-- `respo.css` - CSS utilities
-- `respo.cursor` - Cursor management for nested states
-
----
-
-## Essential Calcit CLI Commands for Development
-
-### 1. Exploration and Discovery
-
-```bash
-# List all namespaces in the project
-cr query ns
-
-# Get details about a specific namespace (imports, definitions)
-cr query ns respo.core
-cr query ns respo.app.core
-
-# List all definitions in a namespace
-cr query defs respo.core
-cr query defs respo.app.updater
-
-# Quick peek at a definition (signature, parameters, docs)
-cr query peek respo.core/defcomp
-cr query peek respo.core/render!
-
-# Get complete definition as JSON syntax tree
-cr query def respo.core/render!
-cr query def respo.app.core/dispatch!
-
-# Search for a symbol across all namespaces
-cr query find render!
-cr query find *store
-
-# Find all usages of a specific definition
-cr query usages respo.core/render!
-cr query usages respo.app.core/dispatch!
-```
-
-### 2. Precise Code Navigation (tree pattern)
-
-When you need to understand or modify specific parts of a definition:
-
-```bash
-# Step 1: Read the complete definition first
-cr query def respo.app.updater/updater
-
-# Step 2: Use tree show to examine the structure (limit depth to reduce output)
-cr tree show respo.app.updater/updater -p "" -d 1 # View root level
-
-# Step 3: Dive deeper into specific indices
-cr tree show respo.app.updater/updater -p "2" -d 1 # Check 3rd element
-cr tree show respo.app.updater/updater -p "2,1" -d 1 # Check 2nd child of 3rd element
-
-# Step 4: Confirm target location before editing
-cr tree show respo.app.updater/updater -p "2,1,0" # Final confirmation
-
-# Step 5: Use tree commands for surgical modifications
-# JSON inline (recommended)
-cr tree replace respo.app.updater/updater -p "2,1,0" -j '"new-value"'
-# Or from stdin
-echo '"new-value"' | cr tree replace respo.app.updater/updater -p "2,1,0" -s -J
-```
-
-echo '["defn", "hello", [], ["println", "|Hello"]]' | cr edit def respo.app.core/hello -s -J
-
-### 3. Code Modification (Agent Optimized)
-
-**Best Practice: Use JSON AST**
-For LLM Agents, **JSON inline (`-j`) is the most reliable method** for code generation. It avoids whitespace/indentation ambiguity inherent in Cirru.
-
-**Input Modes:**
-
-- `-j ''`: **Recommended.** Inline JSON string. Escape quotes carefully.
-- `-e ''`: Inline Cirru one-liner. Good for short, simple expressions.
-- `-f ` / `-s`: Read from file/stdin (defaults to Cirru).
-- `-J`: Combine with `-f`/`-s` to indicate JSON input.
-
-**JSON AST Structure Guide:**
-
-- Function: `(defn f (x) x)` -> `["defn", "f", ["x"], "x"]`
-- Map: `{:a 1}` -> `["{}", [":a", "1"]]`
-- String: `"|hello"` -> `"|hello"` (in JSON string: `"\"|hello\""`)
-- Keyword: `:key` -> `":key"`
-
-**Common Commands:**
-
-```bash
-# 1. Add/Update Definition (JSON)
-# (defn greet (name) (println "|Hello" name))
-cr edit def respo.demo/greet -j '["defn", "greet", ["name"], ["println", "\"|Hello\"", "name"]]'
-
-# 2. Add Definition (Cirru One-liner - risky for complex code)
-cr edit def respo.demo/simple -e 'defn simple (x) (+ x 1)'
-
-# 3. Update Imports (JSON)
-# (ns respo.demo (:require [respo.core :refer [div span]]))
-cr edit imports respo.demo -j '[["respo.core", ":refer", ["div", "span"]]]'
-
-# 4. Remove Definition
-cr edit rm-def respo.demo/old-fn
-
-# 5. Namespace Operations
-cr edit add-ns respo.new-feature
-cr edit rm-ns respo.deprecated
-```
-
-**💡 Pro Tip: Validation**
-If unsure about the JSON structure, generate it from Cirru first:
-
-```bash
-cr cirru parse -O 'defn f (x) (+ x 1)'
-# Output: ["defn", "f", ["x"], ["+", "x", "1"]]
-```
-
-### 4. Project Configuration
-
-```bash
-# Get project configuration (init-fn, reload-fn, version)
-cr query config
-
-# Set project configuration
-cr edit config version "0.16.22"
-cr edit config init-fn "respo.main/main!"
-cr edit config reload-fn "respo.main/reload!"
-```
-
-### 5. Workflow: Building From Scratch
-
-Follow this sequence to create a new feature cleanly:
-
-**Step 1: Create Namespace**
-
-```bash
-cr edit add-ns respo.app.feature-x
-```
-
-**Step 2: Add Imports**
-Define dependencies (e.g., `respo.core`).
-
-```bash
-# Cirru: (:require [respo.core :refer [defcomp div span]])
-cr edit imports respo.app.feature-x -j '[["respo.core", ":refer", ["defcomp", "div", "span"]]]'
-```
-
-**Step 3: Create Component**
-Define the component logic.
-
-```bash
-# Cirru: (defcomp comp-x (data) (div {} (<> "Feature X")))
-cr edit def respo.app.feature-x/comp-x -j '["defcomp", "comp-x", ["data"], ["div", ["{}"], ["<>", "\"|Feature X\""]]]'
-```
-
-**Step 4: Verify**
-
-```bash
-cr query def respo.app.feature-x/comp-x
-cr --check-only
-```
-
-**Step 5: Integrate**
-Mount or use it in `respo.app.comp.container`.
-
-```bash
-# 1. Add import to container ns
-cr edit require respo.app.comp.container respo.app.feature-x
-
-# 2. Add usage (using surgical edit)
-# Find where to insert using `cr tree show ...`
-# cr tree insert-child ... -j '["respo.app.feature-x/comp-x", "data"]'
-```
-
-### 6. Documentation and Language
-
-```bash
-# Check for syntax errors and warnings
-cr --check-only
-cr js --check-only
-
-# Get language documentation
-cr docs api render!
-cr docs ref component
-cr docs list-api # List all API docs
-cr docs list-guide # List all guide docs
-
-# Parse Cirru code to JSON (for understanding syntax)
-cr cirru parse '(div {} (<> "hello"))'
-
-# Format JSON to Cirru code
-cr cirru format '["div", {}, ["<>", "hello"]]'
-
-# Parse EDN to JSON
-cr cirru parse-edn '{:a 1 :b [2 3]}'
-
-# Show Cirru syntax guide (read before generating Cirru)
-cr cirru show-guide
-```
-
-### 6. Library Management
-
-```bash
-# List official libraries
-cr libs
-
-# Search libraries by keyword
-cr libs search router
-
-# Read library README from GitHub
-cr libs readme respo
-
-# Install/update dependencies
-caps
-```
-
-### 7. Code Analysis
-
-```bash
-# Call graph analysis from init-fn (or custom root)
-cr analyze call-graph
-cr analyze call-graph --root app.main/main! --ns-prefix app. --include-core --max-depth 5 --format json
-
-# Call count statistics
-cr analyze count-calls
-cr analyze count-calls --root app.main/main! --ns-prefix app. --include-core --format json --sort count
-```
-
----
-
-## Development Workflow for LLM Agents
-
-### Step 1: Understand the Problem
-
-```bash
-# Always start by exploring related code
-cr query ns respo.app.updater # Understand state management
-cr query find my-function-name # Find where it's defined/used
-cr query usages respo.core/render! # See how render! is used
-```
-
-### Step 2: Implement the Solution
-
-Use the **precise editing pattern** for complex changes:
-
-```bash
-# 1. Read the whole definition
-cr query def namespace/function-name
-
-# 2. Map out the structure with tree show
-cr tree show namespace/function-name -p "" -d 1
-
-# 3. Navigate to target position
-cr tree show namespace/function-name -p "2,1" -d 1
-
-# 4. Make the change (JSON inline recommended)
-cr tree replace namespace/function-name -p "2,1,0" -j '["new", "code"]'
-
-# Or from stdin (JSON format)
-echo '["new", "code"]' | cr tree replace namespace/function-name -p "2,1,0" -s -J
-
-# 5. Verify
-cr tree show namespace/function-name -p "2,1"
-```
-
-### Step 3: Test and Validate
-
-```bash
-# Check syntax without running
-cr --check-only
-
-# Compile to JavaScript and check for errors
-cr js --check-only
-
-# Run the app once to test
-cr -1
-
-# Compile to JavaScript once
-cr -1 js
-
-# Watch mode (will call reload! on code changes)
-cr
-```
-
-### Step 4: Debug Issues
-
-```bash
-# Check for error messages
-cr query error
-
-# Read error stack traces
-cat .calcit-error.cirru # (if it exists)
-
-# Search for the problematic code
-cr query find problem-symbol
-cr query usages namespace/definition
-
-# Review the definition in detail
-cr query def namespace/definition
-```
-
----
-
-## Common Patterns and Best Practices
-
-### 1. Component Definition Pattern
-
-**Cirru (Read):**
-
-```cirru
-; Standard component structure
-defcomp comp-name (param1 param2 & options)
- div $ {}
- :class-name "|component-name"
- :style $ comp-style
- <> "|Content"
-```
-
-**JSON AST (Write - for `cr edit`):**
-
-```json
-[
- "defcomp",
- "comp-name",
- ["param1", "param2", "&", "options"],
- [
- "div",
- ["{}", [":class-name", "|component-name"], [":style", "comp-style"]],
- ["<>", "|Content"]
- ]
-]
-```
-
-### 2. State Management Pattern
-
-```cirru
-; Define store atom at app.core level
-defatom *store $ {}
- :states $ {}
- :data $ {}
-
-; Create dispatcher
-defn dispatch! (op)
- reset! *store (updater @*store op)
-
-; Updater function pattern
-defn updater (store op)
- tag-match op
- (:action-name value) $
- assoc store :data (process-action (:data store) value)
- (:nested-action id op2) $
- update-in store [:data :nested id] (process-nested op2)
- _ store
-```
-
-### 3. Rendering Pattern
-
-```cirru
-; Initial render
-defn render-app! ()
- render! mount-point (comp-container @*store) dispatch!
-
-; Watch for store changes
-add-watch *store :changes $ fn ()
- render-app!
-
-; Hot reload with cache clearing
-defn reload! ()
- remove-watch *store :changes
- add-watch *store :changes $ fn ()
- render-app!
- clear-cache!
- render-app!
-```
-
-### 4. DOM Element Creation
-
-```cirru
-; Using predefined elements (defn wrappers for create-element)
-div $ {} (<> "text")
-button $ {} (<> "Click me")
-input $ {:value "|default"}
-span $ {:class-name "|style-name"} (<> "content")
-
-; Dynamic elements with create-element
-create-element :custom-tag $ {:prop-name "|value"}
- <> "|child"
-
-; List rendering with list->
-list-> $ {}
- :style $ {} (:display "|flex")
- , $ {}
- :a $ comp-item item-1
- :b $ comp-item item-2
- :c $ comp-item item-3
-```
-
-### 5. Styling Pattern
-
-```cirru
-; Define styles as maps
-def style-container $ {}
- :display "|flex"
- :padding "|10px"
- :background-color "|#f0f0f0"
-
-; Conditional styles
-defn style-for-state (state)
- if (= state :active)
- assoc style-container :background-color "|#3388ff"
- style-container
-
-; Merge styles
-let
- base $ {} (:color "|black")
- extended $ merge base $ {} (:font-size 14)
- extended
-```
-
-**Testing Style to String Conversion:**
-
-```bash
-# Basic example (thread-first pipeline avoids bash escaping issues)
-cr eval 'thread-first ({} (:display "|flex") (:color "|red") (:padding "|10px")) .to-list respo.render.dom/style->string println' --dep respo.calcit/
-# Output: padding:10px;color:red;display:flex;
-```
-
-**Notes:**
-
-- `--dep respo.calcit/` loads the module from `~/.config/calcit/modules/`
-- `thread-first` (or `->`) chains operations: create map → convert to list → style->string → print
-- Direct `ns/def` format to reference functions from loaded modules
-
-**Inline Style Object Format:**
-
-```cirru
-# Map format (key-value pairs)
-my-styles $ {}
- :display "|flex"
- :color "|red"
- :padding "|10px"
- :font-size "|14px"
-```
-
-### 6. Event Handling
-
-**DOM Event Handlers:**
-
-```cirru
-; Simple click handler
-div
- {}
- :on-click $ fn (e dispatch!)
- dispatch! [:button-clicked]
-
-; Input with value tracking
-input
- {}
- :value "|current-value"
- :on-input $ fn (e dispatch!)
- let
- value (e.target.value)
- dispatch! [:input-changed value]
-
-; Keyboard events
-div
- {}
- :on-keydown $ fn (e dispatch!)
- when (= (e.key) "|Enter")
- dispatch! [:submit-form]
-```
-
-**Component-Level Event Listeners:**
-
-Components can define custom listeners that respond to events sent via `send-to-component!`. This is useful for global shortcuts, external triggers, or testing.
-
-```cirru
-; Define a listener function that returns a RespoListener record
-defn on-keydown (cursor state)
- %{} respo.schema/RespoListener (:name :on-keydown)
- :handler $ fn (event dispatch!)
- tag-match event $
- :keydown info
- when
- and
- = |m $ :key info
- :ctrl info
- ; Handle Ctrl+M shortcut
- dispatch! $ :: :states cursor
- assoc state :message "|Shortcut triggered!"
-
-; Use listener in component by including it in the component body
-defcomp comp-with-listener (states data)
- let
- cursor $ :cursor states
- state $ either (:data states) ({})
- [] (on-keydown cursor state) ; Add listener to component
- div $ {}
- <> $ :message state
-```
-
-**Triggering Component Listeners:**
-
-Use `send-to-component!` (from `respo.controller.client`) to programmatically send events to the component tree:
-
-```cirru
-; Send keyboard event to all listening components
-send-to-component! $ :: :keydown
- {} $ :key "|m"
- :ctrl true
-
-; Trigger from timer or external source
-js/window.setTimeout
- fn ()
- send-to-component! $ :: :custom-event
- {} $ :data |some-value
- , 1000
-
-; Useful for:
-; - Global keyboard shortcuts (Ctrl+S, Escape, etc.)
-; - WebSocket message handlers
-; - Timer-based triggers
-; - Testing component event handlers
-```
-
----
-
-## Debugging Common Issues
-
-### Issue: Component not re-rendering
-
-**Diagnosis**:
-
-```bash
-# Check if render-app! is being called
-cr query find render-app!
-cr query usages respo.main/render-app!
-
-# Verify store watcher is set up
-cr query def respo.app.core/dispatch!
-cr query def respo.main/main!
-```
-
-**Solution Pattern**:
-
-```cirru
-; Ensure watch is on *store
-add-watch *store :changes $ fn ()
- render-app!
-
-; Ensure clear-cache! is called on reload
-defn reload! ()
- remove-watch *store :changes
- clear-cache!
- add-watch *store :changes $ fn ()
- render-app!
- render-app!
-```
-
-### Issue: State not updating
-
-**Diagnosis**:
-
-```bash
-# Check updater function logic
-cr query def respo.app.updater/updater
-
-# Verify dispatch! is calling updater correctly
-cr query def respo.app.core/dispatch!
-
-# Check the state path in component
-cr query def respo.app.comp.container/comp-container
-```
-
-**Solution Pattern**:
-
-```cirru
-; Verify tag-match pattern matches dispatched action
-tag-match op
- (:action-name params) $
- ; Make sure return value is updated store
- assoc store :data new-value
- _ store ; Default case needed!
-
-; Ensure dispatch! is called with correct tuple
-dispatch! [:action-name actual-value]
-```
-
-### Issue: Component effects not triggering
-
-**Diagnosis**:
-
-```bash
-# Check effect definition
-cr query def respo.core/defeffect # macro documentation
-
-# Find effect in component
-cr query find my-effect
-cr query usages respo.app.comp.task/my-effect
-```
-
-**Solution Pattern**:
-
-```cirru
-; Effects must be first in component body
-defcomp comp-with-effect (props)
- []
- effect-name param1 param2 ; First!
- div $ {} ; Then render
- <> "|content"
-
-; Effect must match action lifecycle
-defeffect my-effect (initial-value)
- (action element at-place?)
- when (= action :mount)
- do (println "|mounted")
- when (= action :update)
- do (println "|updated")
-```
-
-### Issue: Hot reload breaking state
-
-**Diagnosis**:
-
-```bash
-# Check reload! function
-cr query def respo.main/reload!
-
-# Verify clear-cache! is called
-cr query usages respo.core/clear-cache!
-```
-
-**Solution Pattern**:
-
-```cirru
-; clear-cache! must be called during reload
-defn reload! ()
- remove-watch *store :changes
- clear-cache! ; Critical!
- add-watch *store :changes $ fn ()
- render-app!
- render-app!
-```
-
----
-
-## Modification Strategy: Safe Editing Guide
-
-### Before any edit, follow this checklist:
-
-1. **Understand the context**
-
- ```bash
- cr query ns namespace-name # See imports and doc
- cr query peek namespace-name/def-name # See signature
- ```
-
-2. **Map the exact location**
-
- ```bash
- cr tree show namespace-name/def-name -p "" -d 2 # Overview
- cr tree show namespace-name/def-name -p "2" -d 2 # Check section
- cr tree show namespace-name/def-name -p "2,1" -d 2 # Precise location
- ```
-
-3. **Make surgical change**
-
-```bash
-# JSON inline (recommended)
-cr tree replace namespace-name/def-name -p "2,1,0" -j '"new-value"'
-
-# Or from stdin (JSON format)
-echo '"new-value"' | cr tree replace namespace-name/def-name -p "2,1,0" -s -J
-```
-
-4. **Verify immediately**
- ```bash
- cr tree show namespace-name/def-name -p "2,1" # Confirm change
- cr --check-only # Verify syntax
- ```
-
-### Common edit operations:
-
-```bash
-# Replace a value (JSON inline)
-cr tree replace ns/def -p "2,1,0" -j '"new-value"'
-
-# Insert before a position (JSON)
-cr tree insert-before ns/def -p "2,1" -j '["new", "element"]'
-
-# Insert after a position (JSON)
-cr tree insert-after ns/def -p "2,1" -j '["new", "element"]'
-
-# Delete a node
-cr tree delete ns/def -p "2,1,0"
-
-# Insert as child (first child)
-cr tree insert-child ns/def -p "2,1" -j '"child-value"'
-
-# Append as child (last child, from stdin)
-echo '"child-value"' | cr tree append-child ns/def -p "2,1" -s -J
-```
-
----
-
-## Testing and Validation
-
-### Basic validation
-
-```bash
-# Syntax check only (no execution)
-cr --check-only
-
-# Check JavaScript compilation
-cr js --check-only
-
-# Run application once
-cr -1
-
-# Compile to JS once
-cr -1 js
-```
-
-### Test-driven development
-
-```bash
-# Look at test files
-cr query defs respo.test.main
-cr query def respo.test.main/test-fn
-
-# Run tests
-cr -1 ; (if init-fn runs tests)
-```
-
-### Error diagnosis
-
-```bash
-# View error file
-cr query error
-cat .calcit-error.cirru
-
-# Search for the problematic definition
-cr query find problem-name
-
-# Check the full definition
-cr query def namespace/problem-name
-
-# Validate dependencies
-cr query ns namespace-name # Check imports
-```
-
----
-
-## Important Notes for LLM Agents
-
-### ⚠️ Critical Rules
-
-1. **NEVER directly edit `calcit.cirru` or `compact.cirru`** with text editors
-
- - Use `cr edit` commands instead
- - These are serialized AST structures, not human-readable code
-
-2. **ALWAYS use relative paths for documentation links**
-
- - Use `../` and `../../` for navigation
- - This allows easy file discovery for LLM tools
-
-3. **ALWAYS check syntax before assuming it's correct**
-
- ```bash
- cr --check-only
- ```
-
-4. **ALWAYS verify modifications work**
-
- ```bash
- cr tree show namespace/def -p "modified-path" # Confirm change
- cr --check-only # Check syntax
- cr -1 # Test run
- ```
-
-5. **Use peek before def** to reduce token consumption
- ```bash
- cr query peek ns/def # Light summary
- cr query def ns/def # Full AST (use only if needed)
- ```
-
-### 🎯 Optimization Tips for Token Usage
-
-```bash
-# Fast exploration with limited output
-cr query peek respo.core/defcomp # 5-10 lines
-cr query defs respo.app.updater # Quick list
-
-# Slower but comprehensive
-cr query def respo.app.updater/updater # Full JSON AST
-
-# Use -d flag to limit JSON depth
-cr tree show ns/def -p "2,1" -d 1 # Shallow
-cr tree show ns/def -p "2,1" -d 3 # Medium
-cr tree show ns/def -p "2,1" # Full (default)
-
-# Search before diving deep
-cr query find my-function # Find location first
-cr query usages ns/def # See usage patterns
-```
-
-### 📖 Documentation Strategy
-
-When stuck, use these resources in order:
-
-1. This file (Respo-Agent.md) - you are here
-2. [README.md](../README.md) - Project overview and index
-3. [Beginner Guide](./beginner-guide.md) - Conceptual introduction
-4. [API Reference](./api.md) - Specific API documentation
-5. [Guide docs](./guide/) - Detailed topics
-6. `cr docs api ` - Language documentation
-7. Project code itself: `cr query ns `
-
----
-
-## Quick Reference
-
-### Most Used Commands
-
-```bash
-# Exploration (read-only, no changes)
-cr query ns # List namespaces
-cr query ns respo.core # Read namespace details
-cr query defs respo.app.core # List definitions
-cr query peek respo.core/render! # Quick peek
-cr query def respo.core/render! # Full definition
-cr query find render! # Search globally
-cr query usages respo.core/render! # Find usages
-
-# Navigation (precise editing)
-cr tree show ns/def -p "" -d 1 # View structure
-cr tree show ns/def -p "2,1" -d 1 # Drill down
-cr tree show ns/def -p "2,1,0" # Confirm target
-
-# Modification (careful!)
-cr edit def ns/def -j '["defn", "func", [], "body"]'
-cr tree replace ns/def -p "2,1,0" -j '"value"'
-cr edit rm-def ns/def
-
-# Validation
-cr --check-only # Check syntax
-cr query error # View errors
-cr -1 # Test run
-```
-
-### File Paths in Documentation
-
-When referring to files from within `docs/`:
-
-- `./` - same directory
-- `../` - parent (docs/ to root)
-- `../../` - grandparent (docs/apis/ to root)
-
-Example from `docs/apis/defcomp.md`:
-
-```markdown
-- [Back to README](../../README.md)
-- [API Overview](../api.md)
-- [Another API](./render!.md)
-```
diff --git a/package.json b/package.json
index fe92b3d..31ebc53 100644
--- a/package.json
+++ b/package.json
@@ -1,19 +1,21 @@
{
"dependencies": {
- "@calcit/procs": "^0.11.8",
- "@google/genai": "^1.43.0",
+ "@calcit/procs": "^0.12.11",
+ "@google/genai": "^1.44.0",
+ "@tiye/main-fonts": "0.0.1",
"axios": "^1.12.2",
"cirru-color": "^0.2.4",
"copy-text-to-clipboard": "^3.2.2",
+ "dayjs": "^1.11.18",
"feather-icons": "^4.29.2",
"openai": "^6.16.0"
},
"devDependencies": {
"bottom-tip": "^0.1.5",
- "vite": "^8.0.0-beta.11"
+ "vite": "^8.0.0"
},
"scripts": {
- "build": "yarn vite build --base ./ && rm -rfv extension/dist && cp -vr dist extension/"
+ "build": "rm -rfv dist && cr js && yarn vite build --base ./ && rm -rfv extension/dist && cp -vr dist extension/"
},
"version": "0.0.1",
"packageManager": "yarn@4.12.0"
diff --git a/vite.config.mjs b/vite.config.mjs
index 8aef6af..ff17b8f 100644
--- a/vite.config.mjs
+++ b/vite.config.mjs
@@ -1,7 +1,29 @@
-import { defineConfig } from 'vite';
+import { defineConfig } from "vite";
export default defineConfig({
build: {
- minify: false
- }
+ minify: false,
+ rollupOptions: {
+ output: {
+ manualChunks(id) {
+ if (id.includes("@google/genai")) {
+ return "genai";
+ }
+
+ if (id.includes("/openai/") || id.includes("/openai@")) {
+ return "openai";
+ }
+
+ if (
+ id.includes("/node_modules/respo") ||
+ id.includes("/node_modules/reel") ||
+ id.includes("/node_modules/memof") ||
+ id.includes("/node_modules/@calcit/procs")
+ ) {
+ return "vendor-core";
+ }
+ },
+ },
+ },
+ },
});
diff --git a/yarn.lock b/yarn.lock
index a3ba0ab..c837ac3 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -5,14 +5,14 @@ __metadata:
version: 8
cacheKey: 10c0
-"@calcit/procs@npm:^0.11.8":
- version: 0.11.8
- resolution: "@calcit/procs@npm:0.11.8"
+"@calcit/procs@npm:^0.12.11":
+ version: 0.12.11
+ resolution: "@calcit/procs@npm:0.12.11"
dependencies:
"@calcit/ternary-tree": "npm:0.0.25"
- "@cirru/parser.ts": "npm:^0.0.8"
+ "@cirru/parser.ts": "npm:^0.0.9"
"@cirru/writer.ts": "npm:^0.1.7"
- checksum: 10c0/e57d1e1da4f0dbf180045117952cf036bdeca99a9324472ddc00f1deeb8cc44e106923f22613a94b2a4506494e5c88117bd6892a56a5fc15c9de5c4f9806cf3a
+ checksum: 10c0/1b972d6f3bb4fd2291688e3ff528216abbc43f314ffd7ef6f177a2c8a3c07d67963fa6e1062c52a5c91f87ac3724c97da6023dfc167e707178909e012cf50e7d
languageName: node
linkType: hard
@@ -23,10 +23,10 @@ __metadata:
languageName: node
linkType: hard
-"@cirru/parser.ts@npm:^0.0.8":
- version: 0.0.8
- resolution: "@cirru/parser.ts@npm:0.0.8"
- checksum: 10c0/a722a1ae31503cd602d4bb868f9831f7cf0a3d457cde40fc363120271ebb1320c5cabdf051393b5b10ef3e29452c6e25ab8c2b0d981605299312391f528957b6
+"@cirru/parser.ts@npm:^0.0.9":
+ version: 0.0.9
+ resolution: "@cirru/parser.ts@npm:0.0.9"
+ checksum: 10c0/3b13623b8f627ac81adae0cb6a4e3664f0da09115a997ca9b78b8d5d0b0f9b9b18c1ad847e068831cb1052c7da2a00329d2af04421b1024a004eef0ddca5fd7a
languageName: node
linkType: hard
@@ -65,9 +65,9 @@ __metadata:
languageName: node
linkType: hard
-"@google/genai@npm:^1.43.0":
- version: 1.43.0
- resolution: "@google/genai@npm:1.43.0"
+"@google/genai@npm:^1.44.0":
+ version: 1.44.0
+ resolution: "@google/genai@npm:1.44.0"
dependencies:
google-auth-library: "npm:^10.3.0"
p-retry: "npm:^4.6.2"
@@ -78,7 +78,7 @@ __metadata:
peerDependenciesMeta:
"@modelcontextprotocol/sdk":
optional: true
- checksum: 10c0/b20d989340928447b138baeef8cc11b1011f16e8668688ac80831ccff601f0b2490207b0dca33ebbead97bb44a09f7e909c19df2db2f6410caed838543c4cff4
+ checksum: 10c0/38afc41eeeb0b236c166072dc347e65cafcc812d0e0d9364a175f1dabc63a6cad457eb2b1e6c6f341549b56450f028d99952f898d8d623c21197973d12f25a4b
languageName: node
linkType: hard
@@ -154,17 +154,17 @@ __metadata:
languageName: node
linkType: hard
-"@oxc-project/runtime@npm:0.111.0":
- version: 0.111.0
- resolution: "@oxc-project/runtime@npm:0.111.0"
- checksum: 10c0/99f1794e5746ede1c2729f568f627350922be2e4d0c299cfd60b024eb99fbf99c7c5db127072d20647e8f532c7e3e1d30f390ba35880f9afb2dfd81441747f62
+"@oxc-project/runtime@npm:0.115.0":
+ version: 0.115.0
+ resolution: "@oxc-project/runtime@npm:0.115.0"
+ checksum: 10c0/88905181724fcad06d2852969e706a25a7b6c4fadac22dd6aece24b882a947eda7487451e0824781c9dc87b40b2c6ee582790e47fec5a9ba5d27c6e8c6c35bc1
languageName: node
linkType: hard
-"@oxc-project/types@npm:=0.111.0":
- version: 0.111.0
- resolution: "@oxc-project/types@npm:0.111.0"
- checksum: 10c0/c5fea30eeff99fec22b4d6ed758cf5fa83733d5ca8753a14da7207a840f4f9a576473eb44acb5e6c375e18199e5bfc0aed04a601be05de05a8f0080b9e514d57
+"@oxc-project/types@npm:=0.115.0":
+ version: 0.115.0
+ resolution: "@oxc-project/types@npm:0.115.0"
+ checksum: 10c0/47fc31eb3fb3fcf4119955339f92ba2003f9b445834c1a28ed945cd6b9cd833c7ba66fca88aa5277336c2c55df300a593bc67970e544691eceaa486ebe12cb58
languageName: node
linkType: hard
@@ -248,103 +248,124 @@ __metadata:
languageName: node
linkType: hard
-"@rolldown/binding-android-arm64@npm:1.0.0-rc.2":
- version: 1.0.0-rc.2
- resolution: "@rolldown/binding-android-arm64@npm:1.0.0-rc.2"
+"@rolldown/binding-android-arm64@npm:1.0.0-rc.9":
+ version: 1.0.0-rc.9
+ resolution: "@rolldown/binding-android-arm64@npm:1.0.0-rc.9"
conditions: os=android & cpu=arm64
languageName: node
linkType: hard
-"@rolldown/binding-darwin-arm64@npm:1.0.0-rc.2":
- version: 1.0.0-rc.2
- resolution: "@rolldown/binding-darwin-arm64@npm:1.0.0-rc.2"
+"@rolldown/binding-darwin-arm64@npm:1.0.0-rc.9":
+ version: 1.0.0-rc.9
+ resolution: "@rolldown/binding-darwin-arm64@npm:1.0.0-rc.9"
conditions: os=darwin & cpu=arm64
languageName: node
linkType: hard
-"@rolldown/binding-darwin-x64@npm:1.0.0-rc.2":
- version: 1.0.0-rc.2
- resolution: "@rolldown/binding-darwin-x64@npm:1.0.0-rc.2"
+"@rolldown/binding-darwin-x64@npm:1.0.0-rc.9":
+ version: 1.0.0-rc.9
+ resolution: "@rolldown/binding-darwin-x64@npm:1.0.0-rc.9"
conditions: os=darwin & cpu=x64
languageName: node
linkType: hard
-"@rolldown/binding-freebsd-x64@npm:1.0.0-rc.2":
- version: 1.0.0-rc.2
- resolution: "@rolldown/binding-freebsd-x64@npm:1.0.0-rc.2"
+"@rolldown/binding-freebsd-x64@npm:1.0.0-rc.9":
+ version: 1.0.0-rc.9
+ resolution: "@rolldown/binding-freebsd-x64@npm:1.0.0-rc.9"
conditions: os=freebsd & cpu=x64
languageName: node
linkType: hard
-"@rolldown/binding-linux-arm-gnueabihf@npm:1.0.0-rc.2":
- version: 1.0.0-rc.2
- resolution: "@rolldown/binding-linux-arm-gnueabihf@npm:1.0.0-rc.2"
+"@rolldown/binding-linux-arm-gnueabihf@npm:1.0.0-rc.9":
+ version: 1.0.0-rc.9
+ resolution: "@rolldown/binding-linux-arm-gnueabihf@npm:1.0.0-rc.9"
conditions: os=linux & cpu=arm
languageName: node
linkType: hard
-"@rolldown/binding-linux-arm64-gnu@npm:1.0.0-rc.2":
- version: 1.0.0-rc.2
- resolution: "@rolldown/binding-linux-arm64-gnu@npm:1.0.0-rc.2"
+"@rolldown/binding-linux-arm64-gnu@npm:1.0.0-rc.9":
+ version: 1.0.0-rc.9
+ resolution: "@rolldown/binding-linux-arm64-gnu@npm:1.0.0-rc.9"
conditions: os=linux & cpu=arm64 & libc=glibc
languageName: node
linkType: hard
-"@rolldown/binding-linux-arm64-musl@npm:1.0.0-rc.2":
- version: 1.0.0-rc.2
- resolution: "@rolldown/binding-linux-arm64-musl@npm:1.0.0-rc.2"
+"@rolldown/binding-linux-arm64-musl@npm:1.0.0-rc.9":
+ version: 1.0.0-rc.9
+ resolution: "@rolldown/binding-linux-arm64-musl@npm:1.0.0-rc.9"
conditions: os=linux & cpu=arm64 & libc=musl
languageName: node
linkType: hard
-"@rolldown/binding-linux-x64-gnu@npm:1.0.0-rc.2":
- version: 1.0.0-rc.2
- resolution: "@rolldown/binding-linux-x64-gnu@npm:1.0.0-rc.2"
+"@rolldown/binding-linux-ppc64-gnu@npm:1.0.0-rc.9":
+ version: 1.0.0-rc.9
+ resolution: "@rolldown/binding-linux-ppc64-gnu@npm:1.0.0-rc.9"
+ conditions: os=linux & cpu=ppc64 & libc=glibc
+ languageName: node
+ linkType: hard
+
+"@rolldown/binding-linux-s390x-gnu@npm:1.0.0-rc.9":
+ version: 1.0.0-rc.9
+ resolution: "@rolldown/binding-linux-s390x-gnu@npm:1.0.0-rc.9"
+ conditions: os=linux & cpu=s390x & libc=glibc
+ languageName: node
+ linkType: hard
+
+"@rolldown/binding-linux-x64-gnu@npm:1.0.0-rc.9":
+ version: 1.0.0-rc.9
+ resolution: "@rolldown/binding-linux-x64-gnu@npm:1.0.0-rc.9"
conditions: os=linux & cpu=x64 & libc=glibc
languageName: node
linkType: hard
-"@rolldown/binding-linux-x64-musl@npm:1.0.0-rc.2":
- version: 1.0.0-rc.2
- resolution: "@rolldown/binding-linux-x64-musl@npm:1.0.0-rc.2"
+"@rolldown/binding-linux-x64-musl@npm:1.0.0-rc.9":
+ version: 1.0.0-rc.9
+ resolution: "@rolldown/binding-linux-x64-musl@npm:1.0.0-rc.9"
conditions: os=linux & cpu=x64 & libc=musl
languageName: node
linkType: hard
-"@rolldown/binding-openharmony-arm64@npm:1.0.0-rc.2":
- version: 1.0.0-rc.2
- resolution: "@rolldown/binding-openharmony-arm64@npm:1.0.0-rc.2"
+"@rolldown/binding-openharmony-arm64@npm:1.0.0-rc.9":
+ version: 1.0.0-rc.9
+ resolution: "@rolldown/binding-openharmony-arm64@npm:1.0.0-rc.9"
conditions: os=openharmony & cpu=arm64
languageName: node
linkType: hard
-"@rolldown/binding-wasm32-wasi@npm:1.0.0-rc.2":
- version: 1.0.0-rc.2
- resolution: "@rolldown/binding-wasm32-wasi@npm:1.0.0-rc.2"
+"@rolldown/binding-wasm32-wasi@npm:1.0.0-rc.9":
+ version: 1.0.0-rc.9
+ resolution: "@rolldown/binding-wasm32-wasi@npm:1.0.0-rc.9"
dependencies:
"@napi-rs/wasm-runtime": "npm:^1.1.1"
conditions: cpu=wasm32
languageName: node
linkType: hard
-"@rolldown/binding-win32-arm64-msvc@npm:1.0.0-rc.2":
- version: 1.0.0-rc.2
- resolution: "@rolldown/binding-win32-arm64-msvc@npm:1.0.0-rc.2"
+"@rolldown/binding-win32-arm64-msvc@npm:1.0.0-rc.9":
+ version: 1.0.0-rc.9
+ resolution: "@rolldown/binding-win32-arm64-msvc@npm:1.0.0-rc.9"
conditions: os=win32 & cpu=arm64
languageName: node
linkType: hard
-"@rolldown/binding-win32-x64-msvc@npm:1.0.0-rc.2":
- version: 1.0.0-rc.2
- resolution: "@rolldown/binding-win32-x64-msvc@npm:1.0.0-rc.2"
+"@rolldown/binding-win32-x64-msvc@npm:1.0.0-rc.9":
+ version: 1.0.0-rc.9
+ resolution: "@rolldown/binding-win32-x64-msvc@npm:1.0.0-rc.9"
conditions: os=win32 & cpu=x64
languageName: node
linkType: hard
-"@rolldown/pluginutils@npm:1.0.0-rc.2":
- version: 1.0.0-rc.2
- resolution: "@rolldown/pluginutils@npm:1.0.0-rc.2"
- checksum: 10c0/35d3dec35e00ab090d5ff8287e27af98a15da897dc8b034fe0e00d03e0931b9e993603c054be9e8925e2bde040c44c18b48cb8aeea6a261fd1c8f46837038927
+"@rolldown/pluginutils@npm:1.0.0-rc.9":
+ version: 1.0.0-rc.9
+ resolution: "@rolldown/pluginutils@npm:1.0.0-rc.9"
+ checksum: 10c0/fca488fb96b134ccf95b42632b6112b4abb8b3a9688f166fbd627410def2538ee201953717d234ddecbff62dfe4edc4e72c657b01a9d0750134608d767eea5fd
+ languageName: node
+ linkType: hard
+
+"@tiye/main-fonts@npm:0.0.1":
+ version: 0.0.1
+ resolution: "@tiye/main-fonts@npm:0.0.1"
+ checksum: 10c0/6183664fb638f3b8a14254fe6abba8c79abe7c241c30a88985f511dae7dc1174202d7e10dcd3ce64fe071a4e1ee68b46b02463defced0c331fca3c22e995006f
languageName: node
linkType: hard
@@ -603,6 +624,13 @@ __metadata:
languageName: node
linkType: hard
+"dayjs@npm:^1.11.18":
+ version: 1.11.20
+ resolution: "dayjs@npm:1.11.20"
+ checksum: 10c0/8af525e2aa100c8db9923d706c42b2b2d30579faf89456619413a5c10916efc92c2b166e193c27c02eb3174b30aa440ee1e7b72b0a2876b3da651d204db848a0
+ languageName: node
+ linkType: hard
+
"debug@npm:4, debug@npm:^4.3.4":
version: 4.4.3
resolution: "debug@npm:4.4.3"
@@ -1165,99 +1193,99 @@ __metadata:
languageName: node
linkType: hard
-"lightningcss-android-arm64@npm:1.31.1":
- version: 1.31.1
- resolution: "lightningcss-android-arm64@npm:1.31.1"
+"lightningcss-android-arm64@npm:1.32.0":
+ version: 1.32.0
+ resolution: "lightningcss-android-arm64@npm:1.32.0"
conditions: os=android & cpu=arm64
languageName: node
linkType: hard
-"lightningcss-darwin-arm64@npm:1.31.1":
- version: 1.31.1
- resolution: "lightningcss-darwin-arm64@npm:1.31.1"
+"lightningcss-darwin-arm64@npm:1.32.0":
+ version: 1.32.0
+ resolution: "lightningcss-darwin-arm64@npm:1.32.0"
conditions: os=darwin & cpu=arm64
languageName: node
linkType: hard
-"lightningcss-darwin-x64@npm:1.31.1":
- version: 1.31.1
- resolution: "lightningcss-darwin-x64@npm:1.31.1"
+"lightningcss-darwin-x64@npm:1.32.0":
+ version: 1.32.0
+ resolution: "lightningcss-darwin-x64@npm:1.32.0"
conditions: os=darwin & cpu=x64
languageName: node
linkType: hard
-"lightningcss-freebsd-x64@npm:1.31.1":
- version: 1.31.1
- resolution: "lightningcss-freebsd-x64@npm:1.31.1"
+"lightningcss-freebsd-x64@npm:1.32.0":
+ version: 1.32.0
+ resolution: "lightningcss-freebsd-x64@npm:1.32.0"
conditions: os=freebsd & cpu=x64
languageName: node
linkType: hard
-"lightningcss-linux-arm-gnueabihf@npm:1.31.1":
- version: 1.31.1
- resolution: "lightningcss-linux-arm-gnueabihf@npm:1.31.1"
+"lightningcss-linux-arm-gnueabihf@npm:1.32.0":
+ version: 1.32.0
+ resolution: "lightningcss-linux-arm-gnueabihf@npm:1.32.0"
conditions: os=linux & cpu=arm
languageName: node
linkType: hard
-"lightningcss-linux-arm64-gnu@npm:1.31.1":
- version: 1.31.1
- resolution: "lightningcss-linux-arm64-gnu@npm:1.31.1"
+"lightningcss-linux-arm64-gnu@npm:1.32.0":
+ version: 1.32.0
+ resolution: "lightningcss-linux-arm64-gnu@npm:1.32.0"
conditions: os=linux & cpu=arm64 & libc=glibc
languageName: node
linkType: hard
-"lightningcss-linux-arm64-musl@npm:1.31.1":
- version: 1.31.1
- resolution: "lightningcss-linux-arm64-musl@npm:1.31.1"
+"lightningcss-linux-arm64-musl@npm:1.32.0":
+ version: 1.32.0
+ resolution: "lightningcss-linux-arm64-musl@npm:1.32.0"
conditions: os=linux & cpu=arm64 & libc=musl
languageName: node
linkType: hard
-"lightningcss-linux-x64-gnu@npm:1.31.1":
- version: 1.31.1
- resolution: "lightningcss-linux-x64-gnu@npm:1.31.1"
+"lightningcss-linux-x64-gnu@npm:1.32.0":
+ version: 1.32.0
+ resolution: "lightningcss-linux-x64-gnu@npm:1.32.0"
conditions: os=linux & cpu=x64 & libc=glibc
languageName: node
linkType: hard
-"lightningcss-linux-x64-musl@npm:1.31.1":
- version: 1.31.1
- resolution: "lightningcss-linux-x64-musl@npm:1.31.1"
+"lightningcss-linux-x64-musl@npm:1.32.0":
+ version: 1.32.0
+ resolution: "lightningcss-linux-x64-musl@npm:1.32.0"
conditions: os=linux & cpu=x64 & libc=musl
languageName: node
linkType: hard
-"lightningcss-win32-arm64-msvc@npm:1.31.1":
- version: 1.31.1
- resolution: "lightningcss-win32-arm64-msvc@npm:1.31.1"
+"lightningcss-win32-arm64-msvc@npm:1.32.0":
+ version: 1.32.0
+ resolution: "lightningcss-win32-arm64-msvc@npm:1.32.0"
conditions: os=win32 & cpu=arm64
languageName: node
linkType: hard
-"lightningcss-win32-x64-msvc@npm:1.31.1":
- version: 1.31.1
- resolution: "lightningcss-win32-x64-msvc@npm:1.31.1"
+"lightningcss-win32-x64-msvc@npm:1.32.0":
+ version: 1.32.0
+ resolution: "lightningcss-win32-x64-msvc@npm:1.32.0"
conditions: os=win32 & cpu=x64
languageName: node
linkType: hard
-"lightningcss@npm:^1.31.1":
- version: 1.31.1
- resolution: "lightningcss@npm:1.31.1"
+"lightningcss@npm:^1.32.0":
+ version: 1.32.0
+ resolution: "lightningcss@npm:1.32.0"
dependencies:
detect-libc: "npm:^2.0.3"
- lightningcss-android-arm64: "npm:1.31.1"
- lightningcss-darwin-arm64: "npm:1.31.1"
- lightningcss-darwin-x64: "npm:1.31.1"
- lightningcss-freebsd-x64: "npm:1.31.1"
- lightningcss-linux-arm-gnueabihf: "npm:1.31.1"
- lightningcss-linux-arm64-gnu: "npm:1.31.1"
- lightningcss-linux-arm64-musl: "npm:1.31.1"
- lightningcss-linux-x64-gnu: "npm:1.31.1"
- lightningcss-linux-x64-musl: "npm:1.31.1"
- lightningcss-win32-arm64-msvc: "npm:1.31.1"
- lightningcss-win32-x64-msvc: "npm:1.31.1"
+ lightningcss-android-arm64: "npm:1.32.0"
+ lightningcss-darwin-arm64: "npm:1.32.0"
+ lightningcss-darwin-x64: "npm:1.32.0"
+ lightningcss-freebsd-x64: "npm:1.32.0"
+ lightningcss-linux-arm-gnueabihf: "npm:1.32.0"
+ lightningcss-linux-arm64-gnu: "npm:1.32.0"
+ lightningcss-linux-arm64-musl: "npm:1.32.0"
+ lightningcss-linux-x64-gnu: "npm:1.32.0"
+ lightningcss-linux-x64-musl: "npm:1.32.0"
+ lightningcss-win32-arm64-msvc: "npm:1.32.0"
+ lightningcss-win32-x64-msvc: "npm:1.32.0"
dependenciesMeta:
lightningcss-android-arm64:
optional: true
@@ -1281,7 +1309,7 @@ __metadata:
optional: true
lightningcss-win32-x64-msvc:
optional: true
- checksum: 10c0/c6754b305d4a73652e472fc0d7d65384a6e16c336ea61068eca60de2a45bd5c30abbf012358b82eac56ee98b5d88028932cda5268ff61967cffa400b9e7ee2ba
+ checksum: 10c0/70945bd55097af46fc9fab7f5ed09cd5869d85940a2acab7ee06d0117004a1d68155708a2d462531cea2fc3c67aefc9333a7068c80b0b78dd404c16838809e03
languageName: node
linkType: hard
@@ -1621,14 +1649,14 @@ __metadata:
languageName: node
linkType: hard
-"postcss@npm:^8.5.6":
- version: 8.5.6
- resolution: "postcss@npm:8.5.6"
+"postcss@npm:^8.5.8":
+ version: 8.5.8
+ resolution: "postcss@npm:8.5.8"
dependencies:
nanoid: "npm:^3.3.11"
picocolors: "npm:^1.1.1"
source-map-js: "npm:^1.2.1"
- checksum: 10c0/5127cc7c91ed7a133a1b7318012d8bfa112da9ef092dddf369ae699a1f10ebbd89b1b9f25f3228795b84585c72aabd5ced5fc11f2ba467eedf7b081a66fad024
+ checksum: 10c0/dd918f7127ee7c60a0295bae2e72b3787892296e1d1c3c564d7a2a00c68d8df83cadc3178491259daa19ccc54804fb71ed8c937c6787e08d8bd4bedf8d17044c
languageName: node
linkType: hard
@@ -1708,25 +1736,27 @@ __metadata:
languageName: node
linkType: hard
-"rolldown@npm:1.0.0-rc.2":
- version: 1.0.0-rc.2
- resolution: "rolldown@npm:1.0.0-rc.2"
- dependencies:
- "@oxc-project/types": "npm:=0.111.0"
- "@rolldown/binding-android-arm64": "npm:1.0.0-rc.2"
- "@rolldown/binding-darwin-arm64": "npm:1.0.0-rc.2"
- "@rolldown/binding-darwin-x64": "npm:1.0.0-rc.2"
- "@rolldown/binding-freebsd-x64": "npm:1.0.0-rc.2"
- "@rolldown/binding-linux-arm-gnueabihf": "npm:1.0.0-rc.2"
- "@rolldown/binding-linux-arm64-gnu": "npm:1.0.0-rc.2"
- "@rolldown/binding-linux-arm64-musl": "npm:1.0.0-rc.2"
- "@rolldown/binding-linux-x64-gnu": "npm:1.0.0-rc.2"
- "@rolldown/binding-linux-x64-musl": "npm:1.0.0-rc.2"
- "@rolldown/binding-openharmony-arm64": "npm:1.0.0-rc.2"
- "@rolldown/binding-wasm32-wasi": "npm:1.0.0-rc.2"
- "@rolldown/binding-win32-arm64-msvc": "npm:1.0.0-rc.2"
- "@rolldown/binding-win32-x64-msvc": "npm:1.0.0-rc.2"
- "@rolldown/pluginutils": "npm:1.0.0-rc.2"
+"rolldown@npm:1.0.0-rc.9":
+ version: 1.0.0-rc.9
+ resolution: "rolldown@npm:1.0.0-rc.9"
+ dependencies:
+ "@oxc-project/types": "npm:=0.115.0"
+ "@rolldown/binding-android-arm64": "npm:1.0.0-rc.9"
+ "@rolldown/binding-darwin-arm64": "npm:1.0.0-rc.9"
+ "@rolldown/binding-darwin-x64": "npm:1.0.0-rc.9"
+ "@rolldown/binding-freebsd-x64": "npm:1.0.0-rc.9"
+ "@rolldown/binding-linux-arm-gnueabihf": "npm:1.0.0-rc.9"
+ "@rolldown/binding-linux-arm64-gnu": "npm:1.0.0-rc.9"
+ "@rolldown/binding-linux-arm64-musl": "npm:1.0.0-rc.9"
+ "@rolldown/binding-linux-ppc64-gnu": "npm:1.0.0-rc.9"
+ "@rolldown/binding-linux-s390x-gnu": "npm:1.0.0-rc.9"
+ "@rolldown/binding-linux-x64-gnu": "npm:1.0.0-rc.9"
+ "@rolldown/binding-linux-x64-musl": "npm:1.0.0-rc.9"
+ "@rolldown/binding-openharmony-arm64": "npm:1.0.0-rc.9"
+ "@rolldown/binding-wasm32-wasi": "npm:1.0.0-rc.9"
+ "@rolldown/binding-win32-arm64-msvc": "npm:1.0.0-rc.9"
+ "@rolldown/binding-win32-x64-msvc": "npm:1.0.0-rc.9"
+ "@rolldown/pluginutils": "npm:1.0.0-rc.9"
dependenciesMeta:
"@rolldown/binding-android-arm64":
optional: true
@@ -1742,6 +1772,10 @@ __metadata:
optional: true
"@rolldown/binding-linux-arm64-musl":
optional: true
+ "@rolldown/binding-linux-ppc64-gnu":
+ optional: true
+ "@rolldown/binding-linux-s390x-gnu":
+ optional: true
"@rolldown/binding-linux-x64-gnu":
optional: true
"@rolldown/binding-linux-x64-musl":
@@ -1756,7 +1790,7 @@ __metadata:
optional: true
bin:
rolldown: bin/cli.mjs
- checksum: 10c0/fc97a768b41d40da8e7ef20f9c33795bbeff0fa8effb4704232e19d3cf5b7467a16cf0967a15bd316e70e706a3bd7990a30be99d255da758c7342ee82dee323c
+ checksum: 10c0/d19af14dccf569dc25c0c3c2f1142b7a6f7cec291d55bba80cea71099f89c6d634145bb1b6487626ddd41d578f183f7065ed68067e49d2b964ad6242693b0f79
languageName: node
linkType: hard
@@ -1764,15 +1798,17 @@ __metadata:
version: 0.0.0-use.local
resolution: "root-workspace-0b6124@workspace:."
dependencies:
- "@calcit/procs": "npm:^0.11.8"
- "@google/genai": "npm:^1.43.0"
+ "@calcit/procs": "npm:^0.12.11"
+ "@google/genai": "npm:^1.44.0"
+ "@tiye/main-fonts": "npm:0.0.1"
axios: "npm:^1.12.2"
bottom-tip: "npm:^0.1.5"
cirru-color: "npm:^0.2.4"
copy-text-to-clipboard: "npm:^3.2.2"
+ dayjs: "npm:^1.11.18"
feather-icons: "npm:^4.29.2"
openai: "npm:^6.16.0"
- vite: "npm:^8.0.0-beta.11"
+ vite: "npm:^8.0.0"
languageName: unknown
linkType: soft
@@ -1984,20 +2020,20 @@ __metadata:
languageName: node
linkType: hard
-"vite@npm:^8.0.0-beta.11":
- version: 8.0.0-beta.11
- resolution: "vite@npm:8.0.0-beta.11"
+"vite@npm:^8.0.0":
+ version: 8.0.0
+ resolution: "vite@npm:8.0.0"
dependencies:
- "@oxc-project/runtime": "npm:0.111.0"
- fdir: "npm:^6.5.0"
+ "@oxc-project/runtime": "npm:0.115.0"
fsevents: "npm:~2.3.3"
- lightningcss: "npm:^1.31.1"
+ lightningcss: "npm:^1.32.0"
picomatch: "npm:^4.0.3"
- postcss: "npm:^8.5.6"
- rolldown: "npm:1.0.0-rc.2"
+ postcss: "npm:^8.5.8"
+ rolldown: "npm:1.0.0-rc.9"
tinyglobby: "npm:^0.2.15"
peerDependencies:
"@types/node": ^20.19.0 || >=22.12.0
+ "@vitejs/devtools": ^0.0.0-alpha.31
esbuild: ^0.27.0
jiti: ">=1.21.0"
less: ^4.0.0
@@ -2014,6 +2050,8 @@ __metadata:
peerDependenciesMeta:
"@types/node":
optional: true
+ "@vitejs/devtools":
+ optional: true
esbuild:
optional: true
jiti:
@@ -2036,7 +2074,7 @@ __metadata:
optional: true
bin:
vite: bin/vite.js
- checksum: 10c0/26290592aba7f9caf0476e1194683504787aaab5ffcc6c1d02276ae0e7d03fc2ed59dfb9279528b0780542761ed3a7ce0007688fc6b942f44b11abf55c39400b
+ checksum: 10c0/2246d3d54788dcd53c39da82da3f934a760756642ba9a575c84c5ef9f310bc47697f7f9fde6721fa566675e93e408736b4ac068008d2ddbd75b0ed99c7fd4c67
languageName: node
linkType: hard