The recursive `read-items` function in `app.util.sse/read-stream`
caused a synchronous stack overflow when reading buffered stream
data. Each `rx/mapcat` call chained another recursive invocation
on the same call stack without yielding to the event loop.
Replace the recursive pattern with an `rx/create`-based async pump
that uses Promise `.then()` chaining, keeping the call stack depth
constant regardless of stream size.
Also add progress reporting with names and IDs during binfile
export and import, and bump `eventsource-parser` dependency.
Closes#9470
Signed-off-by: Andrey Antukh <niwi@niwi.nz>
The recursive `read-items` function in `app.util.sse/read-stream`
caused a synchronous stack overflow when reading buffered stream
data. Each `rx/mapcat` call chained another recursive invocation
on the same call stack without yielding to the event loop.
Replace the recursive pattern with an `rx/create`-based async pump
that uses Promise `.then()` chaining, keeping the call stack depth
constant regardless of stream size.
Also add progress reporting with names and IDs during binfile
export and import, and bump `eventsource-parser` dependency.
Closes#9470
Signed-off-by: Andrey Antukh <niwi@niwi.nz>
Closing the fill dialog while an image-fill upload is still in flight
(or while a gradient is mid-edit) leaves the colorpicker's
current-color with only :opacity — no :image, :gradient, or :color.
update-colorpicker-color's WatchEvent then constructed
`(add-recent-color partial)`, which runs the value through
`clr/check-color` and threw "expected valid color". The user saw an
Internal Assertion Error toast and lost the in-flight upload.
The existing `ignore-color?` guard reads `:type` from the *output* of
`get-color-from-colorpicker-state` — but that helper strips :type from
its result, so the guard never actually fires. Add a schema-based gate
(same validator add-recent-color itself uses) right before `rx/of`, so
a partial selection is silently dropped instead of crashing the
workspace. Behaviour for fully-valid colors is unchanged.
Tests cover three cases: (1) a partial image-tab state with only
:opacity returns nil from watch (was: throws); (2) the same partial
shape on the color tab also returns nil — pinning down that the prior
:type guard wouldn't have caught it; (3) a fully-populated plain color
still produces a watch observable so the guard isn't over-eager.
Closes#8443
Co-authored-by: Andrey Antukh <niwi@niwi.nz>
Rename component from link-button to link-button* and remove the legacy
::mf/wrap-props false metadata. Update all callsites to use the modern
[:> lb/link-button* ...] syntax instead of [:& lb/link-button ...].
Part of the #9260 issue.
Signed-off-by: Andrey Antukh <niwi@niwi.nz>
Step toward issue #9260 (incremental migration of legacy UI
components to the modern `*`-suffixed syntax, removing the per-render
JS-to-Clojure props conversion overhead).
Twin namespaces with parallel structure: each defines six components
that drive a recursive text rendering pass over the editor's content
tree (root -> paragraph-set -> paragraph -> node -> text). Both files
were uniformly legacy: every component carried `::mf/wrap-props
false` and read its props with `(obj/get props "key")`. None had
`::mf/register`, `unchecked-get` or `obj/merge!`, so they qualify as
clean Case-A migrations.
frontend/src/app/main/ui/shapes/text/fo_text.cljs (6 components)
----------------------------------------------------------------
- `render-text` -> `render-text*`
- `render-root` -> `render-root*`
- `render-paragraph-set` -> `render-paragraph-set*`
- `render-paragraph` -> `render-paragraph*`
- `render-node` -> `render-node*` (forward-props case,
see below)
- `text-shape` -> `text-shape*` (`::mf/forward-ref`
preserved)
The four leaf components switch from `[props]` + per-key
`(obj/get props "key")` to standard destructuring. `text-shape`
already used destructuring under `::mf/props :obj`; that legacy
metadata is dropped because the modern `*` form handles props
automatically. Its single `::mf/forward-ref true` is kept per the
prompt's "preserve forward-ref" rule.
`render-node` is the recursive driver. It needs to forward all of
its incoming props to the matched paragraph-* / text component and
then to a child `render-node*` after overriding `:node`, `:index`
and `:key`. The migrated form uses `::mf/props :obj` together with
`{:keys [node] :as props}` to keep the JS-object props symbol
available, and `(mf/spread-props props {…})` replaces the previous
`obj/clone` + `obj/set!` chain.
`app.util.object` is no longer required by this namespace and the
`(:require ... [app.util.object :as obj] ...)` line is removed.
frontend/src/app/main/ui/shapes/text/html_text.cljs (6 components)
-----------------------------------------------------------------
Identical six-component shape as `fo_text.cljs`, plus a `code?`
flag threaded through every component to switch the rendering path
between regular shapes and code-style shapes.
- `render-text` -> `render-text*`
- `render-root` -> `render-root*`
- `render-paragraph-set` -> `render-paragraph-set*`
- `render-paragraph` -> `render-paragraph*`
- `render-node` -> `render-node*` (same forward-props
treatment as above,
plus `is-code` in
the spread)
- `text-shape` -> `text-shape*` (`::mf/forward-ref`
preserved)
The `code?` boolean prop is renamed to `is-code` per the migration
prompt's "?-suffixed boolean -> `is-` prefix" rule. The rename is
applied at every read site (5 components) and at the `text-shape*`
internal call to `render-node*`, so the prop is consistent inside
the namespace.
`app.util.object` is no longer required by this namespace either
and the corresponding `:require` line is dropped.
External call sites (3 files, 4 sites)
--------------------------------------
- `frontend/src/app/main/ui/shapes/text.cljs` - the legacy
text-shape wrapper (intentionally kept legacy in this PR because
it dispatches to `svg/text-shape`, which is still being touched by
the in-flight PR #9016) now calls `[:> fo/text-shape* props]`.
The `props` symbol is the wrapper's incoming JS-object; modern
destructured components accept JS-object props at the call site
via `[:>` so this works unchanged.
- `frontend/src/app/util/code_gen/markup_html.cljs` -
`(mf/element text/text-shape #js {:shape shape :code? true})`
becomes
`(mf/element text/text-shape* #js {:shape shape :is-code true})`
(component renamed and the `code?` JS key updated to match the
renamed prop).
- `frontend/src/app/main/ui/workspace/shapes/text/viewport_texts_html.cljs`
- `[:& html/text-shape {…}]` -> `[:> html/text-shape* {…}]`.
Behavior preserved verbatim
---------------------------
Same render output, same forward-ref forwarding semantics, same
recursive children-by-index keying, same default `:dir "auto"` on
`render-paragraph*`. The visible-prop changes are only the `code?`
-> `is-code` rename, all driven from this namespace and its single
caller in `markup_html.cljs`.
Github #9260
Signed-off-by: FairyPigDev <luislee3108@gmail.com>
When the Stripe checkout fails to start, the subscription page now
shows an inline error in the Business Nitrate card under the CTA
instead of a toast. When the post-payment activation fails, the toast
message is updated to point users to support@penpot.app.
The nitrate-form modal also passed a URI object to
build-nitrate-callback-urls while the underlying append-query-param
relied on lambdaisland's u/parse, which only accepts strings. Switched
to the local u/uri helper so both strings and URI records work, so
failures opened from the modal land on the subscription page.
Adopts the rumext * suffix convention for the following components,
invoking them with the [:> JS-style syntax to match the rest of the
codebase (see e.g. rea*, single-selection* in viewport/selection):
- measurements: size-display, distance-display-pill, selection-rect,
distance-display, selection-guides, measurement
- shapes/svg-defs: svg-node, svg-defs (also drop the now-redundant
{::mf/wrap-props false} annotations)
Updates all call sites in inspect/selection_feedback, shapes/shape,
workspace/viewport, and workspace/viewport_wasm. Pure rename — no
behavioral change.
Signed-off-by: bitcompass <devwiz.sh@gmail.com>
Co-authored-by: Andrey Antukh <niwi@niwi.nz>
Step toward issue #9260 (incremental migration of legacy UI
components to the modern `*`-suffixed syntax, removing the per-render
JS-to-Clojure props conversion overhead).
Two unrelated namespaces, both clean Case-A migrations grouped in a
single PR for review efficiency.
frontend/src/app/main/ui/shapes/text/fontfaces.cljs
---------------------------------------------------
Three components, all previously using `::mf/wrap-props false` with a
custom memoizer (`#(mf/memo' % (mf/check-props ["fonts"]))`) and
reading `fonts` via `(obj/get props "fonts")`. The custom memoizer
existed because the legacy components received raw JS-object props,
where the default `mf/memo` Clojure-equality comparison would always
fail.
- `fontfaces-style-html` → `fontfaces-style-html*`
- `fontfaces-style-render` → `fontfaces-style-render*`
- `fontfaces-style` → `fontfaces-style*`
Migration:
- Standard destructuring `[{:keys [fonts]}]` replaces the
`[props]` + `(obj/get props "fonts")` pattern.
- `::mf/wrap-props false` removed.
- Custom memoizer collapses to `::mf/wrap [mf/memo]`. With modern
destructuring the props are Clojure data, so default `=`-based memo
is structurally correct (and a stronger guarantee than the previous
shallow JS-prop check).
- `app.util.object` require dropped from the namespace — it was only
used for the `obj/get props "fonts"` reads that are now gone.
- Internal call site of `fontfaces-style-render*` (within
`fontfaces-style*`) keeps its `[:>` form, just with the new name.
External call sites updated:
- `frontend/src/app/main/render.cljs` — three sites
(`[:& ff/fontfaces-style {:fonts fonts}]` × 3) →
`[:> ff/fontfaces-style* {:fonts fonts}]`.
- `frontend/src/app/main/ui/workspace/shapes.cljs` — one site,
call signature unchanged. (Note: this caller passes `:shapes`
rather than `:fonts`; the legacy component already ignored
`:shapes` because it only read `(obj/get props "fonts")`, so the
modern destructuring `{:keys [fonts]}` preserves the same
behavior. Pre-existing bug, intentionally left out of scope.)
frontend/src/app/main/ui/viewer/thumbnails.cljs
-----------------------------------------------
Four components, all using standard destructuring already (no
`::mf/wrap-props false`, no `unchecked-get`). Migration is the
straight `*` rename plus `?`-prop renames per the prompt's mapping:
- `thumbnails-content` → `thumbnails-content*` (`expanded?` →
`is-expanded`)
- `thumbnails-summary` → `thumbnails-summary*` (no `?`-props)
- `thumbnail-item` → `thumbnail-item*` (`selected?` →
`is-selected`; `::mf/wrap [mf/memo #(mf/deferred …)]` preserved)
- `thumbnails-panel` → `thumbnails-panel*` (`show?` →
`show` — no `is-` prefix needed, reads naturally as a verb)
Internal callsites of all three sub-components in `thumbnails-panel*`
updated to `[:> …*` with renamed kwargs (`:expanded?` →
`:is-expanded`, `:selected?` → `:is-selected`).
The `expanded?` symbol still appears in `thumbnails-panel*`'s body —
it's a local `let`-binding deref'd from the `expanded-state` atom,
not a component param, so the `?` suffix is preserved per the
prompt's "local bindings stay" rule.
External call sites updated:
- `frontend/src/app/main/ui/viewer.cljs` — one site, plus the
`:refer [thumbnails-panel]` → `:refer [thumbnails-panel*]` require
update.
Github #9260
Signed-off-by: FairyPigDev <luislee3108@gmail.com>
Co-authored-by: Andrey Antukh <niwi@niwi.nz>