defineStack accepts two actions sharing one name without a word — including two GLOBAL ones, where the <object>:<name> handler map then silently shadows one #14124

Description

@os-warren

Found while adding an object-bound twin of an object-less action in the duly app (objectstack-ai/duly#27), where "just declare it twice under one name" was the first shape considered. Measured before it was rejected.

@objectstack/spec 17.2.0.

What was measured

Two actions in one stack's actions collection may share a name. defineStack accepts both, emits nothing, and both survive into stack.actions:

// one global + one object-bound, both named `duly_dup_probe`
stack.actions = ["global:duly_dup_probe", "duly_catalog_item:duly_dup_probe"]
object.actions = ["duly_dup_probe/BOUND"] // the bound one merged in
// two GLOBALS, both named `duly_dup2`
outcome = accepted
stack.actions = ["duly_dup2/FIRST", "duly_dup2/SECOND"]

The cross-reference walk is awake for the adjacent case — a nav runAction naming an action that does not exist throws defineStack cross-reference validation failed (1 issue). It says nothing about a runAction whose name resolves to two actions.

Why the two-globals case is the sharp one

Handler dispatch is an exact-string Map lookup on <object>:<name> (executeAction), and registration is keyed the same way. Two globals sharing a name therefore collapse to one key: whichever handler registers second wins, and the other action is a live, declared, permission-gated button whose handler is unreachable. Nothing at author time, build time or boot says so — the declaration is legal, the registration succeeds, and the loser fails only when a user clicks it.

The global + object-bound pair is less lethal (the two occupy distinct keys, and resolveRouteActionDeclaration prefers the object's embedded array) but still ambiguous for every by-name consumer: nav runAction, validate-action-name-refs, and ql.registry.getItem('action', name), which is keyed by bare name and can only return one of them.

Shape

This is the ADR-0078 / ADR-0049 family arriving at the collection level rather than at a key: the metadata parses, renders and reads as wired, and one of the two declarations quietly does nothing. It is also exactly the shape an AI-authored app produces — copying a declaration to place it somewhere new and forgetting to rename it is the natural edit, and no gate catches it.

Suggested direction

Reject a duplicate <objectName ?? 'global'>:<name> within one stack's action set at defineStack time, with the offending pair named. If the global + object-bound pair is judged legitimate (one action, two placements), reject only the same-scope collision and leave that one alone — but then the by-name consumers above need a documented precedence rather than an implicit one.

Worked around in the application by giving the twin a distinct name (duly_catalog_apply_to_people), which is what we would want authors to do anyway — but that was a measurement result, not something the platform told us.

Filing unassigned for triage.

Generated by Claude Code

Metadata

Metadata

Assignees

Labels

Type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions

    , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
     blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
    }
    } catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
    })();
    (function(){
    try {
    var __m = "github.com";
    var __re = new RegExp('^' + "github\\.com" + '
    
    Skip to content

    defineStack accepts two actions sharing one name without a word — including two GLOBAL ones, where the &lt;object&gt;:&lt;name&gt; handler map then silently shadows one #14124

    Description

    @os-warren

    Found while adding an object-bound twin of an object-less action in the duly app (objectstack-ai/duly#27), where "just declare it twice under one name" was the first shape considered. Measured before it was rejected.

    @objectstack/spec 17.2.0.

    What was measured

    Two actions in one stack's actions collection may share a name. defineStack accepts both, emits nothing, and both survive into stack.actions:

    // one global + one object-bound, both named `duly_dup_probe`
    stack.actions = ["global:duly_dup_probe", "duly_catalog_item:duly_dup_probe"]
    object.actions = ["duly_dup_probe/BOUND"] // the bound one merged in
    // two GLOBALS, both named `duly_dup2`
    outcome = accepted
    stack.actions = ["duly_dup2/FIRST", "duly_dup2/SECOND"]
    

    The cross-reference walk is awake for the adjacent case — a nav runAction naming an action that does not exist throws defineStack cross-reference validation failed (1 issue). It says nothing about a runAction whose name resolves to two actions.

    Why the two-globals case is the sharp one

    Handler dispatch is an exact-string Map lookup on <object>:<name> (executeAction), and registration is keyed the same way. Two globals sharing a name therefore collapse to one key: whichever handler registers second wins, and the other action is a live, declared, permission-gated button whose handler is unreachable. Nothing at author time, build time or boot says so — the declaration is legal, the registration succeeds, and the loser fails only when a user clicks it.

    The global + object-bound pair is less lethal (the two occupy distinct keys, and resolveRouteActionDeclaration prefers the object's embedded array) but still ambiguous for every by-name consumer: nav runAction, validate-action-name-refs, and ql.registry.getItem('action', name), which is keyed by bare name and can only return one of them.

    Shape

    This is the ADR-0078 / ADR-0049 family arriving at the collection level rather than at a key: the metadata parses, renders and reads as wired, and one of the two declarations quietly does nothing. It is also exactly the shape an AI-authored app produces — copying a declaration to place it somewhere new and forgetting to rename it is the natural edit, and no gate catches it.

    Suggested direction

    Reject a duplicate <objectName ?? 'global'>:<name> within one stack's action set at defineStack time, with the offending pair named. If the global + object-bound pair is judged legitimate (one action, two placements), reject only the same-scope collision and leave that one alone — but then the by-name consumers above need a documented precedence rather than an implicit one.

    Worked around in the application by giving the twin a distinct name (duly_catalog_apply_to_people), which is what we would want authors to do anyway — but that was a measurement result, not something the platform told us.

    Filing unassigned for triage.

    Generated by Claude Code

    Metadata

    Metadata

    Assignees

    Labels

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions

      , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
      Skip to content

      defineStack accepts two actions sharing one name without a word — including two GLOBAL ones, where the &lt;object&gt;:&lt;name&gt; handler map then silently shadows one #14124

      Description

      @os-warren

      Found while adding an object-bound twin of an object-less action in the duly app (objectstack-ai/duly#27), where "just declare it twice under one name" was the first shape considered. Measured before it was rejected.

      @objectstack/spec 17.2.0.

      What was measured

      Two actions in one stack's actions collection may share a name. defineStack accepts both, emits nothing, and both survive into stack.actions:

      // one global + one object-bound, both named `duly_dup_probe`
      stack.actions = ["global:duly_dup_probe", "duly_catalog_item:duly_dup_probe"]
      object.actions = ["duly_dup_probe/BOUND"] // the bound one merged in
      // two GLOBALS, both named `duly_dup2`
      outcome = accepted
      stack.actions = ["duly_dup2/FIRST", "duly_dup2/SECOND"]
      

      The cross-reference walk is awake for the adjacent case — a nav runAction naming an action that does not exist throws defineStack cross-reference validation failed (1 issue). It says nothing about a runAction whose name resolves to two actions.

      Why the two-globals case is the sharp one

      Handler dispatch is an exact-string Map lookup on <object>:<name> (executeAction), and registration is keyed the same way. Two globals sharing a name therefore collapse to one key: whichever handler registers second wins, and the other action is a live, declared, permission-gated button whose handler is unreachable. Nothing at author time, build time or boot says so — the declaration is legal, the registration succeeds, and the loser fails only when a user clicks it.

      The global + object-bound pair is less lethal (the two occupy distinct keys, and resolveRouteActionDeclaration prefers the object's embedded array) but still ambiguous for every by-name consumer: nav runAction, validate-action-name-refs, and ql.registry.getItem('action', name), which is keyed by bare name and can only return one of them.

      Shape

      This is the ADR-0078 / ADR-0049 family arriving at the collection level rather than at a key: the metadata parses, renders and reads as wired, and one of the two declarations quietly does nothing. It is also exactly the shape an AI-authored app produces — copying a declaration to place it somewhere new and forgetting to rename it is the natural edit, and no gate catches it.

      Suggested direction

      Reject a duplicate <objectName ?? 'global'>:<name> within one stack's action set at defineStack time, with the offending pair named. If the global + object-bound pair is judged legitimate (one action, two placements), reject only the same-scope collision and leave that one alone — but then the by-name consumers above need a documented precedence rather than an implicit one.

      Worked around in the application by giving the twin a distinct name (duly_catalog_apply_to_people), which is what we would want authors to do anyway — but that was a measurement result, not something the platform told us.

      Filing unassigned for triage.

      Generated by Claude Code

      Metadata

      Metadata

      Assignees

      Labels

      Type

      Projects

      No projects

        Milestone

        No milestone

        Relationships

        None yet

        Development

        No branches or pull requests

        Issue actions

        , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
        Skip to content

        defineStack accepts two actions sharing one name without a word — including two GLOBAL ones, where the &lt;object&gt;:&lt;name&gt; handler map then silently shadows one #14124

        Description

        @os-warren

        Found while adding an object-bound twin of an object-less action in the duly app (objectstack-ai/duly#27), where "just declare it twice under one name" was the first shape considered. Measured before it was rejected.

        @objectstack/spec 17.2.0.

        What was measured

        Two actions in one stack's actions collection may share a name. defineStack accepts both, emits nothing, and both survive into stack.actions:

        // one global + one object-bound, both named `duly_dup_probe`
        stack.actions = ["global:duly_dup_probe", "duly_catalog_item:duly_dup_probe"]
        object.actions = ["duly_dup_probe/BOUND"] // the bound one merged in
        // two GLOBALS, both named `duly_dup2`
        outcome = accepted
        stack.actions = ["duly_dup2/FIRST", "duly_dup2/SECOND"]
        

        The cross-reference walk is awake for the adjacent case — a nav runAction naming an action that does not exist throws defineStack cross-reference validation failed (1 issue). It says nothing about a runAction whose name resolves to two actions.

        Why the two-globals case is the sharp one

        Handler dispatch is an exact-string Map lookup on <object>:<name> (executeAction), and registration is keyed the same way. Two globals sharing a name therefore collapse to one key: whichever handler registers second wins, and the other action is a live, declared, permission-gated button whose handler is unreachable. Nothing at author time, build time or boot says so — the declaration is legal, the registration succeeds, and the loser fails only when a user clicks it.

        The global + object-bound pair is less lethal (the two occupy distinct keys, and resolveRouteActionDeclaration prefers the object's embedded array) but still ambiguous for every by-name consumer: nav runAction, validate-action-name-refs, and ql.registry.getItem('action', name), which is keyed by bare name and can only return one of them.

        Shape

        This is the ADR-0078 / ADR-0049 family arriving at the collection level rather than at a key: the metadata parses, renders and reads as wired, and one of the two declarations quietly does nothing. It is also exactly the shape an AI-authored app produces — copying a declaration to place it somewhere new and forgetting to rename it is the natural edit, and no gate catches it.

        Suggested direction

        Reject a duplicate <objectName ?? 'global'>:<name> within one stack's action set at defineStack time, with the offending pair named. If the global + object-bound pair is judged legitimate (one action, two placements), reject only the same-scope collision and leave that one alone — but then the by-name consumers above need a documented precedence rather than an implicit one.

        Worked around in the application by giving the twin a distinct name (duly_catalog_apply_to_people), which is what we would want authors to do anyway — but that was a measurement result, not something the platform told us.

        Filing unassigned for triage.

        Generated by Claude Code

        Metadata

        Metadata

        Assignees

        Labels

        Type

        Projects

        No projects

          Milestone

          No milestone

          Relationships

          None yet

          Development

          No branches or pull requests

          Issue actions

          , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
          Skip to content

          defineStack accepts two actions sharing one name without a word — including two GLOBAL ones, where the &lt;object&gt;:&lt;name&gt; handler map then silently shadows one #14124

          Description

          @os-warren

          Found while adding an object-bound twin of an object-less action in the duly app (objectstack-ai/duly#27), where "just declare it twice under one name" was the first shape considered. Measured before it was rejected.

          @objectstack/spec 17.2.0.

          What was measured

          Two actions in one stack's actions collection may share a name. defineStack accepts both, emits nothing, and both survive into stack.actions:

          // one global + one object-bound, both named `duly_dup_probe`
          stack.actions = ["global:duly_dup_probe", "duly_catalog_item:duly_dup_probe"]
          object.actions = ["duly_dup_probe/BOUND"] // the bound one merged in
          // two GLOBALS, both named `duly_dup2`
          outcome = accepted
          stack.actions = ["duly_dup2/FIRST", "duly_dup2/SECOND"]
          

          The cross-reference walk is awake for the adjacent case — a nav runAction naming an action that does not exist throws defineStack cross-reference validation failed (1 issue). It says nothing about a runAction whose name resolves to two actions.

          Why the two-globals case is the sharp one

          Handler dispatch is an exact-string Map lookup on <object>:<name> (executeAction), and registration is keyed the same way. Two globals sharing a name therefore collapse to one key: whichever handler registers second wins, and the other action is a live, declared, permission-gated button whose handler is unreachable. Nothing at author time, build time or boot says so — the declaration is legal, the registration succeeds, and the loser fails only when a user clicks it.

          The global + object-bound pair is less lethal (the two occupy distinct keys, and resolveRouteActionDeclaration prefers the object's embedded array) but still ambiguous for every by-name consumer: nav runAction, validate-action-name-refs, and ql.registry.getItem('action', name), which is keyed by bare name and can only return one of them.

          Shape

          This is the ADR-0078 / ADR-0049 family arriving at the collection level rather than at a key: the metadata parses, renders and reads as wired, and one of the two declarations quietly does nothing. It is also exactly the shape an AI-authored app produces — copying a declaration to place it somewhere new and forgetting to rename it is the natural edit, and no gate catches it.

          Suggested direction

          Reject a duplicate <objectName ?? 'global'>:<name> within one stack's action set at defineStack time, with the offending pair named. If the global + object-bound pair is judged legitimate (one action, two placements), reject only the same-scope collision and leave that one alone — but then the by-name consumers above need a documented precedence rather than an implicit one.

          Worked around in the application by giving the twin a distinct name (duly_catalog_apply_to_people), which is what we would want authors to do anyway — but that was a measurement result, not something the platform told us.

          Filing unassigned for triage.

          Generated by Claude Code

          Metadata

          Metadata

          Assignees

          Labels

          Type

          Projects

          No projects

            Milestone

            No milestone

            Relationships

            None yet

            Development

            No branches or pull requests

            Issue actions

            , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
            Skip to content

            defineStack accepts two actions sharing one name without a word — including two GLOBAL ones, where the &lt;object&gt;:&lt;name&gt; handler map then silently shadows one #14124

            Description

            @os-warren

            Found while adding an object-bound twin of an object-less action in the duly app (objectstack-ai/duly#27), where "just declare it twice under one name" was the first shape considered. Measured before it was rejected.

            @objectstack/spec 17.2.0.

            What was measured

            Two actions in one stack's actions collection may share a name. defineStack accepts both, emits nothing, and both survive into stack.actions:

            // one global + one object-bound, both named `duly_dup_probe`
            stack.actions = ["global:duly_dup_probe", "duly_catalog_item:duly_dup_probe"]
            object.actions = ["duly_dup_probe/BOUND"] // the bound one merged in
            // two GLOBALS, both named `duly_dup2`
            outcome = accepted
            stack.actions = ["duly_dup2/FIRST", "duly_dup2/SECOND"]
            

            The cross-reference walk is awake for the adjacent case — a nav runAction naming an action that does not exist throws defineStack cross-reference validation failed (1 issue). It says nothing about a runAction whose name resolves to two actions.

            Why the two-globals case is the sharp one

            Handler dispatch is an exact-string Map lookup on <object>:<name> (executeAction), and registration is keyed the same way. Two globals sharing a name therefore collapse to one key: whichever handler registers second wins, and the other action is a live, declared, permission-gated button whose handler is unreachable. Nothing at author time, build time or boot says so — the declaration is legal, the registration succeeds, and the loser fails only when a user clicks it.

            The global + object-bound pair is less lethal (the two occupy distinct keys, and resolveRouteActionDeclaration prefers the object's embedded array) but still ambiguous for every by-name consumer: nav runAction, validate-action-name-refs, and ql.registry.getItem('action', name), which is keyed by bare name and can only return one of them.

            Shape

            This is the ADR-0078 / ADR-0049 family arriving at the collection level rather than at a key: the metadata parses, renders and reads as wired, and one of the two declarations quietly does nothing. It is also exactly the shape an AI-authored app produces — copying a declaration to place it somewhere new and forgetting to rename it is the natural edit, and no gate catches it.

            Suggested direction

            Reject a duplicate <objectName ?? 'global'>:<name> within one stack's action set at defineStack time, with the offending pair named. If the global + object-bound pair is judged legitimate (one action, two placements), reject only the same-scope collision and leave that one alone — but then the by-name consumers above need a documented precedence rather than an implicit one.

            Worked around in the application by giving the twin a distinct name (duly_catalog_apply_to_people), which is what we would want authors to do anyway — but that was a measurement result, not something the platform told us.

            Filing unassigned for triage.

            Generated by Claude Code

            Metadata

            Metadata

            Assignees

            Labels

            Type

            Projects

            No projects

              Milestone

              No milestone

              Relationships

              None yet

              Development

              No branches or pull requests

              Issue actions

              , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
              Skip to content

              defineStack accepts two actions sharing one name without a word — including two GLOBAL ones, where the &lt;object&gt;:&lt;name&gt; handler map then silently shadows one #14124

              Description

              @os-warren

              Found while adding an object-bound twin of an object-less action in the duly app (objectstack-ai/duly#27), where "just declare it twice under one name" was the first shape considered. Measured before it was rejected.

              @objectstack/spec 17.2.0.

              What was measured

              Two actions in one stack's actions collection may share a name. defineStack accepts both, emits nothing, and both survive into stack.actions:

              // one global + one object-bound, both named `duly_dup_probe`
              stack.actions = ["global:duly_dup_probe", "duly_catalog_item:duly_dup_probe"]
              object.actions = ["duly_dup_probe/BOUND"] // the bound one merged in
              // two GLOBALS, both named `duly_dup2`
              outcome = accepted
              stack.actions = ["duly_dup2/FIRST", "duly_dup2/SECOND"]
              

              The cross-reference walk is awake for the adjacent case — a nav runAction naming an action that does not exist throws defineStack cross-reference validation failed (1 issue). It says nothing about a runAction whose name resolves to two actions.

              Why the two-globals case is the sharp one

              Handler dispatch is an exact-string Map lookup on <object>:<name> (executeAction), and registration is keyed the same way. Two globals sharing a name therefore collapse to one key: whichever handler registers second wins, and the other action is a live, declared, permission-gated button whose handler is unreachable. Nothing at author time, build time or boot says so — the declaration is legal, the registration succeeds, and the loser fails only when a user clicks it.

              The global + object-bound pair is less lethal (the two occupy distinct keys, and resolveRouteActionDeclaration prefers the object's embedded array) but still ambiguous for every by-name consumer: nav runAction, validate-action-name-refs, and ql.registry.getItem('action', name), which is keyed by bare name and can only return one of them.

              Shape

              This is the ADR-0078 / ADR-0049 family arriving at the collection level rather than at a key: the metadata parses, renders and reads as wired, and one of the two declarations quietly does nothing. It is also exactly the shape an AI-authored app produces — copying a declaration to place it somewhere new and forgetting to rename it is the natural edit, and no gate catches it.

              Suggested direction

              Reject a duplicate <objectName ?? 'global'>:<name> within one stack's action set at defineStack time, with the offending pair named. If the global + object-bound pair is judged legitimate (one action, two placements), reject only the same-scope collision and leave that one alone — but then the by-name consumers above need a documented precedence rather than an implicit one.

              Worked around in the application by giving the twin a distinct name (duly_catalog_apply_to_people), which is what we would want authors to do anyway — but that was a measurement result, not something the platform told us.

              Filing unassigned for triage.

              Generated by Claude Code

              Metadata

              Metadata

              Assignees

              Labels

              Type

              Projects

              No projects

                Milestone

                No milestone

                Relationships

                None yet

                Development

                No branches or pull requests

                Issue actions

                , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
                Skip to content

                defineStack accepts two actions sharing one name without a word — including two GLOBAL ones, where the &lt;object&gt;:&lt;name&gt; handler map then silently shadows one #14124

                Description

                @os-warren

                Found while adding an object-bound twin of an object-less action in the duly app (objectstack-ai/duly#27), where "just declare it twice under one name" was the first shape considered. Measured before it was rejected.

                @objectstack/spec 17.2.0.

                What was measured

                Two actions in one stack's actions collection may share a name. defineStack accepts both, emits nothing, and both survive into stack.actions:

                // one global + one object-bound, both named `duly_dup_probe`
                stack.actions = ["global:duly_dup_probe", "duly_catalog_item:duly_dup_probe"]
                object.actions = ["duly_dup_probe/BOUND"] // the bound one merged in
                // two GLOBALS, both named `duly_dup2`
                outcome = accepted
                stack.actions = ["duly_dup2/FIRST", "duly_dup2/SECOND"]
                

                The cross-reference walk is awake for the adjacent case — a nav runAction naming an action that does not exist throws defineStack cross-reference validation failed (1 issue). It says nothing about a runAction whose name resolves to two actions.

                Why the two-globals case is the sharp one

                Handler dispatch is an exact-string Map lookup on <object>:<name> (executeAction), and registration is keyed the same way. Two globals sharing a name therefore collapse to one key: whichever handler registers second wins, and the other action is a live, declared, permission-gated button whose handler is unreachable. Nothing at author time, build time or boot says so — the declaration is legal, the registration succeeds, and the loser fails only when a user clicks it.

                The global + object-bound pair is less lethal (the two occupy distinct keys, and resolveRouteActionDeclaration prefers the object's embedded array) but still ambiguous for every by-name consumer: nav runAction, validate-action-name-refs, and ql.registry.getItem('action', name), which is keyed by bare name and can only return one of them.

                Shape

                This is the ADR-0078 / ADR-0049 family arriving at the collection level rather than at a key: the metadata parses, renders and reads as wired, and one of the two declarations quietly does nothing. It is also exactly the shape an AI-authored app produces — copying a declaration to place it somewhere new and forgetting to rename it is the natural edit, and no gate catches it.

                Suggested direction

                Reject a duplicate <objectName ?? 'global'>:<name> within one stack's action set at defineStack time, with the offending pair named. If the global + object-bound pair is judged legitimate (one action, two placements), reject only the same-scope collision and leave that one alone — but then the by-name consumers above need a documented precedence rather than an implicit one.

                Worked around in the application by giving the twin a distinct name (duly_catalog_apply_to_people), which is what we would want authors to do anyway — but that was a measurement result, not something the platform told us.

                Filing unassigned for triage.

                Generated by Claude Code

                Metadata

                Metadata

                Assignees

                Labels

                Type

                Projects

                No projects

                  Milestone

                  No milestone

                  Relationships

                  None yet

                  Development

                  No branches or pull requests

                  Issue actions