Skip to content

Disable formatting, layout, and restructuring commands on metaprogramming attributes - #6056

Open
BayuAri with Copilot wants to merge 7 commits into
mainfrom
copilot/fix-metaprogramming-bold-crash
Open

BayuAri with Copilot wants to merge 7 commits into
mainfrom
copilot/fix-metaprogramming-bold-crash

Conversation

Copilot AI commented Oct 19, 2026 •

Copy link
Copy Markdown
Contributor

With the cursor on a metaprogramming attribute such as =favorite, Bold turned the value into <b>=favorite</b>, and a second Bold threw Invalid path … No thought found with id …. Every command that formats, lays out, or restructures the cursor thought had the same exposure, including Swap Parent and Swap Note.

Change

  • New selector: isAttributeSelected(state, filter?). True when any selectedPaths entry is an attribute. It uses some, so a multiselect that includes an attribute disables the command outright.

  • canExecute on 33 commands. Each adds && !isAttributeSelected(state):

    • Bold, Italic, Underline, Strikethrough, Code, Clear Formatting, Headings
    • Text Color, Letter Case
    • Table View, Context View, Prose View, Sort, Sort Picker, Bullet Style, Mark as done
    • Pin, Pin All, Pin Descendants
    • Split Sentences, Join, Extract Subthought, Extract Category, Define Term, Generate Emoji, Generate Thought
    • Categorize, Uncategorize, Bind Context, Note, Swap Grandparent, Swap Parent, Swap Note

    Sort, Sort Picker, and Bullet Style had no canExecute before. With no cursor their behaviour is unchanged, because selectedPaths is empty.

  • preventDefault: true on Bold, Italic, and Underline. A disabled shortcut otherwise fell through to the browser's own contenteditable Cmd/Ctrl+B/I/U, which formatted a selected =favorite and hit the same crash. Text Color already set this flag.

    • Side effect for review: these three shortcuts now never reach the browser, even with no thought selected. On Windows and Linux that includes Ctrl+U (view source).
canExecute: state => isDocumentEditable() && (!!state.cursor || hasMulticursor(state)) && !isAttributeSelected(state),
preventDefault: true,

Tests

  • metaprogrammingAttributes.ts is table-driven over all 33 commands. Each runs on a normal thought, not on an attribute, and not on a multiselect that contains one.
  • bold.ts adds a regression test for the issue's steps.
  • The generateThought test that ran with an attribute as the replacement target is removed, because that case is now disabled.

Docs

  • commands.md adds the attribute rule to the canExecute contract and updates the Generate Thought target description.
  • metaprogramming.md notes which commands are disabled on attributes.

Not addressed

Co-Authored-By: GitHub Copilot unknown (unknown) <223556219+Copilot@users.noreply.github.com>

Co-authored-by: BayuAri <8419585+BayuAri@users.noreply.github.com>
@github-actions

github-actions Bot commented Oct 19, 2026 •

Copy link
Copy Markdown
Contributor
Preview Deployment · Oct 21, 2026, 2:47 AM UTC · e3cecb0

Preview deployment

…ming attributes

Co-Authored-By: GitHub Copilot unknown (unknown) <223556219+Copilot@users.noreply.github.com>

Co-authored-by: BayuAri <8419585+BayuAri@users.noreply.github.com>
Copilot AI changed the title [WIP] Fix crashing when reapplying bold on metaprogramming attributes Disable formatting, layout, and restructuring commands on metaprogramming attributes Oct 19, 2026
Copilot AI requested a review from BayuAri October 19, 2026 14:36
@BayuAri

BayuAri commented Oct 20, 2026 •

Copy link
Copy Markdown
Collaborator

Issue B: Outdent is active on Meta Programming

- aaa

Step to Reproduce

  1. Set aaa as Favorite
  2. Tap Show Hidden button
  3. Place the focus on =favorite
  4. Tap Outdent button

Current behavior

=favorite is outdented the same level as its parent aaa
This is also valid for Share button.
This is also valid for using Gesture Command and selecting Command from Command Universe

image

Expected behavior

Outdent and Share buttons should be disabled when focus is on Metaprogramming.
Gesture and Command Universe should not be applied onto Metaprogramming

@BayuAri

BayuAri commented Oct 20, 2026 •

Copy link
Copy Markdown
Collaborator

Issue C: Applying text color onto Metaprogramming

- aaa

Step to Reproduce

  1. Set aaa as Favorite
  2. Tap Show Hidden button
  3. Place the focus on =favorite
  4. Cmd+Opt+3

Current behavior

=favorite color is yellow
After text color applied, restriction for Metaprogramming is lifted, other command / buttons and commands are enabled (it was previously disabled)

image

Clicking Yellow Text Color on Color Swatch after step 4 brings the crash to the app
image

Expected behavior

Metaprogramming should not allow text or background color applied from command shortcut

@BayuAri

BayuAri commented Oct 20, 2026

Copy link
Copy Markdown
Collaborator

Issue D: Favorite button is active on Metaprogramming

- aaa

Step to Reproduce

  1. Set aaa as Favorite
  2. Tap Show Hidden button
  3. Place the focus on =favorite
  4. Tap Favorite button

Current behavior

=favorite is favorited

image

Expected behavior

Favorite buttons should be disabled when focus is on Metaprogramming.

@BayuAri

BayuAri commented Oct 20, 2026

Copy link
Copy Markdown
Collaborator

Issue E: Clear Thought Command is active on Metaprogramming

- aaa

Step to Reproduce

  1. Set aaa as Favorite
  2. Tap Show Hidden button
  3. Place the focus on =favorite
  4. Cmd+Shift+Opt+C

Current behavior

=favorite is cleared

image image

Expected behavior

Clear Thought command should be disabled when focus is on Metaprogramming.
Clear Thought should be greyed out on Command Universe when focus is on Metaprogramming.
Clear Thought gesture should be disabled when focus is on Metaprogramming.

@BayuAri

BayuAri commented Oct 20, 2026

Copy link
Copy Markdown
Collaborator

@copilot Please rework this PR according to the plan below. It replaces the current approach of a fixed list of 33 commands, which still let QA Issues B–E (comments above) through. Push the changes to this PR branch.

PR #6056 — Metaprogramming attributes are read-only

Problem

PR #6056 blocks a hand-picked list of 33 commands via !isAttributeSelected(state). QA comments (Issues B–E) show the blocklist leaks: Outdent, Export/Share, Favorite, Apply Color (Cmd+Opt+n), Clear Thought still run on =favorite via toolbar, keyboard, gesture and Command Universe, and any new command will leak too. Applying color turns the value into <font…>=favorite</font>, which no longer passes isAttribute, so the restriction "lifts" and the swatch then crashes.

Decisions (confirmed with user)

  • Attributes are read-only. An attribute and everything inside it (e.g. =style, color, tomato) can be navigated and viewed (with Show Hidden), but not edited, formatted, moved, deleted or extended.
  • Deny by default. Every command is disabled when any selected path (cursor or multicursor) is inside an attribute subtree, unless the command opts in with allowOnAttribute: true.
  • Attributes change only through commands on the parent (Pin, Favorite, Table View, Sort, Bullet Style, Text Color, …). Typing = to turn a thought into an attribute is blocked.

Allowed on attributes (allowOnAttribute: true)

  • Navigation: cursorUp, cursorDown, cursorBack, cursorForward, cursorNext, cursorPrev, cursorBackTable, cursorForwardTable, moveCursorBackward, moveCursorForward, jumpBack, jumpForward, navigateBack, navigateForward, home, cancel, selectAll, selectBetween
  • Visibility: toggleHiddenThoughts
  • App-level / read-only: help, settings, devices, customizeToolbar, fontSizeUp, fontSizeDown, toggleSidebar, toggleUndoSlider, openCommandCenter, closeCommandCenter, openDesktopCommandUniverse, openMobileCommandUniverse, gestureMenu, copyCursor
  • Global history: undo, redo, repeat (the repeated command is still gated)

Everything else is blocked — incl. delete, archive, clearThought, new thought/subthought family, indent/outdent/move, formatting, color, favorite, pin, export, etc.

Approach

  1. Selectors
    • isInAttribute(state, path): true if any id in the path (ancestor-or-self) has an attribute value.
    • isAttributeSelected(state, filter?) (generalize the PR's): selectedPaths(state, filter).some(path => isInAttribute(state, path)).
  2. Command gate
    • Add allowOnAttribute?: boolean to src/@types/Command.ts.
    • Add canExecuteCommand(command, state) = (command.allowOnAttribute || !isAttributeSelected(state)) && (!command.canExecute || command.canExecute(state)).
    • Use it everywhere canExecute is checked today: src/commands.ts (executeCommand L448, multicursor loop L538, keyDown L978), ToolbarButton, PanelCommand, CommandUniverseGridItem, GestureMenuItem, DesktopCommandUniverse, CommandItem, CommandTableItem, useFilteredCommands.
    • In keyDown, preventDefault when the attribute gate blocks a shortcut. Revert the PR's unconditional preventDefault: true on bold/italic/underline (removes the Ctrl+U side effect).
    • Set allowOnAttribute: true on the allowlist; remove the PR's 33 per-command !isAttributeSelected additions.
  3. Read-only text (src/components/Editable.tsx)
    • When isInAttribute(path), render ContentEditable disabled (no caret, no native formatting, no paste, no IME, mobile keyboard stays closed) while keeping cursor highlight and keyboard navigation.
    • Backstop in the change handler: reject edits to a thought inside an attribute, like the existing =readonly branch.
  4. Block typing = — in the Editable change handler, reject a new value that isAttribute when the old value was not, with an error (e.g. "Metaprogramming attributes can only be set with commands."), and cancel the throttled edit. Covers typing and single-thought paste.
  5. Drag-and-drop — attribute subtrees cannot be dragged (useDragAndDropThought.canDrag) and nothing can be dropped into one (useDragAndDropThought / useDragAndDropSubThought canDrop).
  6. Pickers — ColorPicker swatches, LetterCase, Sort and Bullet pickers no-op/close when the selection is in an attribute (Issue C crash path).
  7. Tests
    • Gate test over globalCommands: every non-allowlisted command is not executable on an attribute, on an attribute descendant, and on a multiselect containing one; allowlisted commands are unaffected.
    • Regressions for QA Issues B–E (outdent, exportContext, favorite, applyColor keyboard, clearThought) + keep the bold regression.
    • Editable: attribute is not contenteditable; typing = into a normal thought is rejected; parent commands (Pin, Favorite, Table View) still add/remove attributes.
  8. Docs — docs/metaprogramming.md (read-only rule, subtree scope, commands-only authoring), docs/commands.md (allowOnAttribute + gate), and the metaprogramming section of src/components/modals/Help.tsx, which currently tells users to type =readonly, =immovable, etc.

Notes / to verify during implementation

  • Note editing: confirm the cursor path does not go through =note while editing a note in Note.tsx; otherwise the gate would block commands during note editing. Note text editing via the Note component stays allowed.
  • Multi-line paste / import: keep importing =… lines (otherwise exported thoughtspaces with metaprogramming can't round-trip). Only direct typing/single-thought edits are blocked. Flag if the user wants import blocked too.
  • Consequence: attributes with no parent command (=readonly, =immovable, =uneditable, =options, =focus, =let, =style beyond Text Color, …) can no longer be created or removed from the UI; existing ones keep working but are locked.
  • Already-corrupted values like <b>=favorite</b> are not detected as attributes; out of scope. The editThought childrenMap fragility noted in the PR is also out of scope.
  • Overlap with Swap Parent: disable on metaprogramming attributes and their children #6006 (Swap Parent on attribute children) is subsumed by the subtree scope.

…and gate

Replaces the per-command isAttributeSelected checks with canExecuteCommand, which disables every command on an attribute or a thought inside one unless it sets allowOnAttribute. The editable is not contenteditable inside an attribute, typing or a single-line paste cannot create an attribute, and drag-and-drop and the pickers are blocked.

Co-Authored-By: GitHub Copilot unknown (unknown) <223556219+Copilot@users.noreply.github.com>

Co-authored-by: BayuAri <8419585+BayuAri@users.noreply.github.com>

Copilot AI commented Oct 20, 2026

Copy link
Copy Markdown
Contributor Author

@copilot Please rework this PR according to the plan below. It replaces the current approach of a fixed list of 33 commands, which still let QA Issues B–E (comments above) through. Push the changes ...

Reworked in 340fafc. Attributes are now read-only behind a deny-by-default gate (canExecuteCommand plus allowOnAttribute), as planned. In desktop Chrome, Outdent, Favorite, Cmd+B, Cmd+Opt+3, Clear Thought, Tab, Shift+Tab and Backspace on =favorite now do nothing and throw no errors. Each of Issues B–E has a regression test.

Three places where I departed from the plan or it had side effects:

  • Move Cursor Forward/Backward are not allowlisted. Outside a table, Tab and Shift+Tab indent and outdent, so allowlisting them still let Shift+Tab outdent =favorite.
  • Thoughts inside =archive can no longer be deleted permanently. They are inside an attribute, so the existing archive test now asserts they stay. If you want archived thoughts deletable, the subtree rule would need an exception for =archive.
  • Single-line paste of =x is blocked in importText. Multi-line paste and import still create attributes.

@github-actions

Copy link
Copy Markdown
Contributor

🖼️ Puppeteer snapshot diffs

@copilot: These snapshot tests failed, which indicates a visual regression. Please review your changes.

1 broken snapshot
drag-and-drop / pinned-drop-hovers-cliff-drop-end-and-root-sibling-thought-drop-1
Expected (base branch) Diff Actual (PR branch)
drag-and-drop / pinned-drop-hovers-cliff-drop-end-and-root-sibling-thought-drop-1

Workflow run

If the visual changes are intentional, update the snapshots for the affected test files:

yarn test:puppeteer -u drag-and-drop

@github-actions
github-actions Bot marked this pull request as ready for review October 20, 2026 05:54
@github-actions

github-actions Bot commented Oct 20, 2026 •

Copy link
Copy Markdown
Contributor

🤖 Copilot conflict resolution

Merge conflicts resolved. The most recent task was this one.

Task 1 of 6. Started by Copilot Conflict Resolution.

…amming-bold-crash

# Conflicts:
#	docs/drag-and-drop.md
#	src/hooks/useDragAndDropSubThought.tsx
#	src/hooks/useDragAndDropThought.tsx

Co-authored-by: raineorshine <750276+raineorshine@users.noreply.github.com>
@BayuAri

BayuAri commented Oct 21, 2026 •

Copy link
Copy Markdown
Collaborator

Issue F: Meta programming children accepts indented thought

- aaa
   - bbb

Step to Reproduce

  1. Favorited aaa
  2. Tap Show Hidden Thought button on the toolbar
  3. Place the focus on bbb
  4. Tap Indent button

Current behavior

bbb is moved as subthought of =favorite
This is valid from Command Universe but not valid using gesture and drop and drag action.

image

Expected behavior

Indent button should be disabled when a subthought is on the same level as a meta programming

@BayuAri BayuAri left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot AI and others added 2 commits October 21, 2026 02:42
…amming-bold-crash

# Conflicts:
#	docs/commands.md

Co-authored-by: BayuAri <8419585+BayuAri@users.noreply.github.com>
Co-Authored-By: GitHub Copilot <223556219+Copilot@users.noreply.github.com>

Copilot AI commented Oct 21, 2026

Copy link
Copy Markdown
Contributor Author

Test cases

Issue F is fixed in e3cecb0, and the merge conflict with main is resolved in 70a4068.

  • Indent: disabled when the previous sibling is an attribute. This applies to the toolbar button, Command Universe, the gesture and the keyboard. The reducer also does nothing in that case.
  • Move Thought Up / Down: fixed the same way. Neither will move the last or first child into an attribute under the neighbouring parent.
  • Conflict: it was in docs/commands.md. I kept main's Generate Thought text but left out its sentence about attribute targets, since a thought inside an attribute can no longer be a target.

Each case has a regression test in metaprogrammingAttributes.ts, and CI is green.

Copilot AI requested a review from BayuAri October 21, 2026 03:00

@BayuAri BayuAri left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@BayuAri

BayuAri commented Oct 21, 2026

Copy link
Copy Markdown
Collaborator

@raineorshine
Please advice on below concern
#6056 (comment)

Consequence: attributes with no parent command (=readonly, =immovable, =uneditable, =options, =focus, =let, =style beyond Text Color, …) can no longer be created or removed from the UI; existing ones keep working but are locked.

@raineorshine

Copy link
Copy Markdown
Contributor

The agent apparently wants to disable editing of metaprogramming attributes, but that is not desirable. They were designed to be editable just like any other thought when hidden thoughts are visible.

Instead of stopping the user from modifying attributes, it should just fall back gracefully. An invalid attribute does nothing at all.

As for the invalid thoughtId error, that is connected to a performance shortcut that exists for O(1) lookup or child attributes by name. However, it is not enforceable at the db level so it probably needs a deeper rethink. Preventing the error without making deeper changes is recommended in the short term.

@raineorshine

raineorshine commented Oct 21, 2026 •

Copy link
Copy Markdown
Contributor

Outdent and Share buttons should be disabled when focus is on Metaprogramming.
Gesture and Command Universe should not be applied onto Metaprogramming
Favorite buttons should be disabled when focus is on Metaprogramming.
...

We don't want to go this far and disable commands on metaprogramming attributes. We should just fix errors and ignore attributes that become invalid. e.g. <b>=favorite</b> is no longer a metaprogramming attribute, it's a normal thought. Let's not be smart about it. The user turned it into a normal thought, so we show it that way. There is no benefit to the user doing that of course, but we don't want to create special cases for everything. We just make sure it doesn't throw an error.

The only way a user could create =favorite/=favorite is if they had hidden thoughts on anyway. At that point anything is fair game. It's the same with other nonsense combinations like =pin/=pin. We generally want to fail gracefully and do nothing. In the future we'll have some UI to show invalid attributes with better error feedback.

It's like HTML itself. If you mistype HTML, it doesn't throw an error. It just looks funny. It's more forgiving than full programming languages that can't run at all if there is a stray semicolon. Not that we humans have to worry about typing semicolons anymore.

Perhaps we'll write up a spec for metaprogramming attributes so agents can understand them and directly manipulate things like =pin and =note. This will become more useful once we have proper dynamic attributes that can reference other thoughts. This is currently supported by just one attribute: =note which can be set to a subthought's value, e.g. =note/=path/Child.

This branch was successfully deployed

1 active deployment
Preview — e3cecb04 Deployed Oct 21, 2026 by github-actions[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Metaprogramming attributes accept formatting and layout commands, and reapplying Bold crashes

3 participants