@@ -9511,9 +9511,14 @@ var e=function(t,n){return e=Object.setPrototypeOf||{__proto__:[]}instanceof Arr
// .zddc files without diverting into the editor. User
// clicks (or tabs) into the editor when they want to type.
autofocus: false,
- // CodeMirror's "nocursor" mode is the truest read-only:
- // selection allowed for copy, no caret, no edit affordances.
- readOnly: !writable ? 'nocursor' : false,
+ // Read-only uses readOnly:true (NOT "nocursor"): the editor
+ // stays focusable so the user can click in, select text, and
+ // copy — they just can't edit. "nocursor" removes the textarea
+ // from focus, which also kills click-drag selection (the whole
+ // reason a viewer would otherwise force admin mode just to copy
+ // a .zddc snippet). autofocus:false keeps arrow-key tree nav
+ // intact until the user deliberately clicks into the editor.
+ readOnly: !writable,
});
// Stash the node on the editor so the lint helper can decide
// whether to apply the .zddc schema layer.
@@ -9681,6 +9686,24 @@ var e=function(t,n){return e=Object.setPrototypeOf||{__proto__:[]}instanceof Arr
return 'File';
}
+ var VERB_NAMES = { r: 'read', w: 'write', c: 'create', d: 'delete', a: 'admin' };
+ function verbsLabel(verbs) {
+ return ['r', 'w', 'c', 'd', 'a']
+ .filter(function (v) { return verbs.indexOf(v) !== -1; })
+ .map(function (v) { return VERB_NAMES[v]; })
+ .join(', ');
+ }
+ // permsValue renders the per-entry verb set the principal holds here.
+ // Server mode: node.verbs ("rwcda" subset). Offline (FS-API) mode has
+ // no ACL — access is whatever the filesystem grants.
+ function permsValue(verbs) {
+ if (typeof verbs !== 'string') {
+ return state.source === 'fs' ? 'local folder (filesystem)' : 'unknown';
+ }
+ if (!verbs) return 'none (read-only)';
+ return verbsLabel(verbs) + ' (' + verbs + ')';
+ }
+
function buildRowsHtml(node) {
var tree = window.app.modules.tree;
var z = window.zddc;
@@ -9739,6 +9762,18 @@ var e=function(t,n){return e=Object.setPrototypeOf||{__proto__:[]}instanceof Arr
if (node.modTime) html += kv('Modified', fmtDate(node.modTime));
if (node.virtual) html += kv('Virtual', 'Not yet created on disk');
+ // ── Effective access for the current principal at this location ──
+ // "Your permissions" is the per-entry verb set (sync, from the
+ // listing). "Your roles" is cascade-scoped — it can differ by
+ // location — so it needs a path-scoped fetch; render a placeholder
+ // that fillRoles() updates once /.profile/access?path= resolves.
+ html += '';
+ html += kv('Your permissions', permsValue(node.verbs));
+ if (state.source === 'server') {
+ html += 'Your roles'
+ + '…';
+ }
+
// Path comes last (longest, most likely to wrap).
var path = tree ? tree.pathFor(node) : '';
if (path) html += kv('Path', path, true);
@@ -9831,6 +9866,25 @@ var e=function(t,n){return e=Object.setPrototypeOf||{__proto__:[]}instanceof Arr
render(node);
position(row);
card.classList.add('is-visible');
+ fillRoles(row, node);
+ }
+
+ // Async-fill the "Your roles" row from the path-scoped access view
+ // (zddc.cap.at memoises per path, so repeat hovers are instant).
+ // Bails if the card has moved to another row before the fetch lands.
+ async function fillRoles(row, node) {
+ if (state.source !== 'server') return;
+ if (!window.zddc || !window.zddc.cap) return;
+ var tree = window.app.modules.tree;
+ var path = tree ? tree.pathFor(node) : '';
+ if (!path) return;
+ var view;
+ try { view = await window.zddc.cap.at(path); } catch (_e) { return; }
+ if (currentRow !== row) return;
+ var el = card && card.querySelector('#hc-roles');
+ if (!el) return;
+ var roles = (view && Array.isArray(view.path_roles)) ? view.path_roles : [];
+ el.textContent = roles.length ? roles.join(', ') : 'none';
}
function init() {
diff --git a/zddc/internal/apps/embedded/classifier.html b/zddc/internal/apps/embedded/classifier.html
index d48a531..f4ffd63 100644
--- a/zddc/internal/apps/embedded/classifier.html
+++ b/zddc/internal/apps/embedded/classifier.html
@@ -1793,7 +1793,7 @@ body.is-elevated::after {