diff --git a/public/index.html b/public/index.html
index 6fe25ac..f373b0d 100644
--- a/public/index.html
+++ b/public/index.html
@@ -93,6 +93,18 @@
background: var(--x-color-border, rgba(127, 127, 127, 0.35));
}
.toolbar-icon-button span { font-size: 16px; line-height: 1; }
+ .toolbar-autosave-warn {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ width: 22px;
+ height: 22px;
+ margin-left: 6px;
+ color: var(--x-color-warning, #c97a00);
+ font-size: 16px;
+ cursor: help;
+ }
+ .toolbar-autosave-warn[hidden] { display: none; }
.panel {
border-right: 1px solid var(--x-color-border, rgba(127, 127, 127, 0.2));
padding: 12px;
diff --git a/src/bareforge/dnd/drag.cljs b/src/bareforge/dnd/drag.cljs
index 2248101..a3cae29 100644
--- a/src/bareforge/dnd/drag.cljs
+++ b/src/bareforge/dnd/drag.cljs
@@ -229,22 +229,22 @@
(let [^js br (.getBoundingClientRect host)
sl (.-scrollLeft host)
st (.-scrollTop host)
- ^js nodes (.querySelectorAll host "[data-bareforge-id]")
- out (volatile! [])]
- (dotimes [i (.-length nodes)]
- (let [^js el (.item nodes i)
- id (.getAttribute el "data-bareforge-id")]
- (when (and id (not= id "root"))
- (let [^js eb (.getBoundingClientRect el)
- left (+ (- (.-left eb) (.-left br)) sl)
- top (+ (- (.-top eb) (.-top br)) st)
- rect {:left left
- :top top
- :right (+ left (.-width eb))
- :bottom (+ top (.-height eb))}]
- (when (rects-overlap? marquee-rect rect)
- (vswap! out conj id))))))
- @out))
+ ^js nodes (.querySelectorAll host "[data-bareforge-id]")]
+ (into []
+ (keep (fn [i]
+ (let [^js el (.item nodes i)
+ id (.getAttribute el "data-bareforge-id")]
+ (when (and id (not= id "root"))
+ (let [^js eb (.getBoundingClientRect el)
+ left (+ (- (.-left eb) (.-left br)) sl)
+ top (+ (- (.-top eb) (.-top br)) st)
+ rect {:left left
+ :top top
+ :right (+ left (.-width eb))
+ :bottom (+ top (.-height eb))}]
+ (when (rects-overlap? marquee-rect rect)
+ id))))))
+ (range (.-length nodes)))))
(defn- commit-marquee! [^js e]
(let [^js host (canvas-el)
diff --git a/src/bareforge/doc/sanitize.cljs b/src/bareforge/doc/sanitize.cljs
index 0131781..6f0d1f4 100644
--- a/src/bareforge/doc/sanitize.cljs
+++ b/src/bareforge/doc/sanitize.cljs
@@ -2,14 +2,20 @@
"Pure sanitisation helpers for the two doc fields that ship raw
user-supplied strings into the DOM: `:inner-html` (raw SVG/HTML on
`:raw-html-slot?` components like x-icon) and `:attrs` URL values
- (`href`, `src`, …).
+ (`href`, `src`, …). Plus an identifier-shape scanner that guards
+ the export pipeline against doc fields that the codegen later
+ interpolates verbatim into emitted JS / CLJS source.
- Two layers of protection:
+ Three layers of protection:
- **Block-list scanners** (`unsafe-findings`): given a doc, return a
vector of `[path reason]` entries naming each suspect site. Used at
the load boundary by `storage/project-file/validate-project` to
refuse a malicious payload outright with an explanatory message.
+ Covers XSS payloads in `:inner-html` / URL attrs **and** unsafe
+ identifier shapes in attr keys, binding keys, field / action
+ names, and trigger action-refs (everything codegen splices into
+ a JS or CLJS string literal).
- **Best-effort sanitisers** (`sanitize-svg-fragment`,
`sanitize-doc`): strip the obvious payloads — `