Bike 2.0 (Preview 273)

  • Changed to optional persistent ids
  • Fixed link IDs not being remapped on import
  • Fixed threading issue when deallocating editor
  • Fixed memory leaks in JavaScript extension runtime

Well shoot, I thought I was done with the big changes…

The “Changed to optional persistent ids” line above is quite far ranging. I think a necessary change, but yipes, touched a lot of different bits of code. My tests are all running again, but I expect I’ve broken some stuff along the way. Please keep an eye out and report back anything that’s off.

It also is a breaking change for AppleScript, Shortcuts, and Typescript API.

The general change:

  • Previously every Bike row had both a numeric id and a persistent string id.
  • In all the automation layers (above) “id” referred to the persistent id.
  • Now those persistent ids are optional, so the automation layers can’t rely on them for ids
  • So instead rows are exposed to the automation layers like this:
Row
   id: number // not persistent in file format
   persistentId: string? // optional, persistent in file format.
   ensuredPersistentId: string // return persistent id, creating if needed

Download:

1 Like

not sure when this behaviour started. if if doubleclick to select a word and drag left, the originally selected word loses its selection

but if i drag right, the originally selected word stays selected

1 Like

I think I can add here that if you double-click to select a word, and then do opt+shift+left, the behaviour is the same—it first deselects the word, then advances selection to the left.

1 Like

Thanks, I’ve just fixed this case.

This is a little inconsistent with above, but I think I’m going to keep this behavior. The system also seems to work this same way. And it’s a pretty edge case, and to solve it would mean adding another bit of state to selection. I think current behavior is good.

1 Like

I’m having fun experimenting with different extensions, but the extension settings functionality doesn’t seem to work for me.

I used Claude Code to create a minimal reproducible example, and I see the setting checkbox in the Extension Settings panel, but checking/unchecking the setting doesn’t seem to do anything.

Also, I’m finding the Logs Explorer helpful, but I don’t see any logs coming from the DOM file Settings.tsx

Please see below for a bug report created via Claude Code.


Bug report: bike.defaults.set() in DOM context not visible to app context

Summary

When an extension’s settings UI (DOM context) calls bike.defaults.set(key, value), the change is not visible to bike.defaults.get(key) in the app context, and bike.defaults.observe(key, ...) in the app context never fires. Restarting Bike does not resolve the issue.

The app context continues to return the value registered via bike.defaults.registerDefaults(), as if the DOM’s write never happened.

Environment

  • Bike version: 273
  • @bike-outliner/extension-kit version: 0.7.1
  • macOS: Tahoe

Minimum reproducible example

Three files, scaffolded from npx bike-ext new test-settings:

manifest.json

{
  "$schema": "../../node_modules/@bike-outliner/extension-kit/schemas/manifest.schema.json",
  "version": "0.1.0",
  "api_version": "0.0.0",
  "permissions": [],
  "host_permissions": []
}

app/main.ts

import { AppExtensionContext, CommandContext } from 'bike/app'

export async function activate(context: AppExtensionContext) {
  console.log('[test-settings app] activate start, get(mySetting) =', bike.defaults.get('mySetting'))

  bike.defaults.registerDefaults({ 'mySetting': true })

  console.log('[test-settings app] after registerDefaults, get(mySetting) =', bike.defaults.get('mySetting'))

  bike.defaults.observe('mySetting', (v) => {
    console.log('[test-settings app] observe(mySetting) fired =', v)
  })

  bike.settings.addItem({
    label: 'Test Settings',
    script: 'Settings.js',
  })

  bike.commands.addCommands({
    commands: {
      'test-settings:show': showCommand,
    },
  })
}

function showCommand(context: CommandContext): boolean {
  const value = bike.defaults.get('mySetting')
  console.log('[test-settings app] showCommand read =', value)
  if (context.editor) {
    context.editor.showStatusMessage(`mySetting = ${String(value)}`, 3000)
  }
  return true
}

dom/Settings.tsx

import { DOMExtensionContext } from 'bike/dom'
import { Checkbox, Disclosure } from 'bike/components'
import { createRoot } from 'react-dom/client'
import { useState } from 'react'

export function activate(context: DOMExtensionContext) {
  console.log('[test-settings dom] activate, get(mySetting) =', bike.defaults.get('mySetting'))
  createRoot(context.element).render(<SettingsPanel />)
}

function SettingsPanel() {
  const [checked, setChecked] = useState(
    () => bike.defaults.get('mySetting') !== false
  )

  function onChange(value: boolean) {
    setChecked(value)
    console.log('[test-settings dom] set(mySetting) =', value)
    bike.defaults.set('mySetting', value)
    console.log('[test-settings dom] after set, get(mySetting) =', bike.defaults.get('mySetting'))
  }

  return (
    <Disclosure label="Test Settings" defaultExpanded>
      <Checkbox checked={checked} onChange={(e) => onChange(e.target.checked)}>
        My test setting
      </Checkbox>
    </Disclosure>
  )
}

Steps to reproduce

  1. Build and install the extension: npx bike-ext build test-settings --install
  2. Launch Bike
  3. Open Settings > Extensions > Test Settings
  4. Uncheck the “My test setting” checkbox
  5. Run the test-settings:show command via the command palette
  6. Quit and relaunch Bike; run the command again

Expected behavior

  • After unchecking the checkbox, the app context’s bike.defaults.observe('mySetting', ...) callback should fire with false
  • Running test-settings:show should display mySetting = false in the status bar
  • After a restart, the setting should remain false

Actual behavior

  • The observe() callback in the app context never fires when the DOM checkbox is toggled
  • Running test-settings:show always displays mySetting = true (the registered default)
  • Restarting Bike does not change this — the app context always sees the default, as if the DOM’s set() was never persisted to or visible to the app side

Logs

Bike’s [notice] channel shows only the app-context logs:

[notice] bike/app/test-settings: [test-settings app] activate start, get(mySetting) = undefined
[notice] bike/app/test-settings: [test-settings app] after registerDefaults, get(mySetting) = true
[notice] bike/app/test-settings: [test-settings app] showCommand read = true

Notably:

  • No [test-settings app] observe(mySetting) fired = ... log ever appears, even after toggling the checkbox multiple times
  • showCommand reads true both before and after the checkbox is unchecked, including across full Bike restarts
  • (DOM-context console.log output from Settings.tsx was not visible in the notice log stream, so I can’t confirm whether the DOM-side bike.defaults.set() is even being invoked — but the React checkbox state does update visually, so onChange is firing.)

Notes

  • The calendar extension in core-extensions uses the same pattern (DOM writes to bike.defaults, app reads from it), but its effects are only observable when creating new date rows with format strings — so a silent fallback to defaults there would be easy to miss.
  • Per api/dom/globals.d.ts and api/app/bike.d.ts, both bike.defaults stores are documented as “backed by UserDefaults with the prefix bike.ext.<extensionId>.” — so they should be the same underlying store.

Ah, another bug! Thanks for testing! Settings is working for the calendar example because it’s loaded first. I think if you disable the calendar extension then this example might also work. I’m fixing so multiple extensions writing from same DOM context will work in next release.

2 Likes

I think fixed in:

Awesome, it’s working now on my end after the update toe 274!