Repository: stateful/vscode-runme
Pull request #115 (2023-01-01 18:18:55)

Basic annotations edit UX (actual UI needs more work)
Here's unfinished impl of how a custom renderer provides an intuitive UI (draft) to allow editing. Clean, intuitive, and in line with notebook UX does.

What's left:
- UI is rough and thrown together quickly. Needs refinement in separate PR. The horizontal only is not an optimal choice since viewport width is rarely wide enough.
- Currently behind feature flag (turn on `annotationsEdit` in vscode settings) so we can merge ahead of additional UI work in separate PR.

![image](https://user-images.githubusercontent.com/250527/212936943-8d003b69-be33-411c-9f27-be52af6f436d.png)

Closes https://github.com/stateful/vscode-runme/pull/116 (as obsolete).

![ux](https://user-images.githubusercontent.com/250527/212925052-6afc1e8b-057a-4dc6-9f8b-24192f3a3fd1.gif)

christian-bromann: @sourishkrout interesting attempt, I haven't thought about using the `controller.createNotebookCellExecution(cell)` API to create an output. That said, my biggest concerns are the interplay between cell output and settings view and the limited UI space available. I am proposing an alternative that I mentioned in the original issue thread using a webview in the right sidebar in #116, what do you think?
sourishkrout: > @sourishkrout interesting attempt, I haven't thought about using the `controller.createNotebookCellExecution(cell)` API to create an output. That said, my biggest concerns are the interplay between cell output and settings view and the limited UI space available. I am proposing an alternative that I mentioned in the original issue thread using a webview in the right sidebar in #116, what do you think?

Having this sidebar alternative in hand reaffirms the solution of this PR (#115). As mentioned, the strong association between (while being distinctly separate structurally) input & output cells is a core tenet of notebooks. Same goes for available space for UI (sidebar scores even lower here - uses up more space less efficiently), especially vertically which in the notebook world is basically infinite since one can always continue scrolling. Same goes for the "settings view" where overflow UI strategies with max-height scrollbars or tabs for subcategories. The interplay between input/output cells is at the core of the notebook UX concept.

Any UI comments you'd like to share @admc amongst the available three options (quick picker, cell output, sidebar)?
admc: I can see the side-by-side webview being the right UX for something like the stackoverflow thread for an error message, or something that needs a bunch of space, but I don't think its right for these annotations. In this case I think we want the UX to be as close to the experience you get when editing raw markdown. So in short, lets tick with the inline cell editing experience thats closely tied to the rest of the cell functionality and doesn't require the users attention going to other places in the vs code UX.
sourishkrout: Works end-to-end.

![ux](https://user-images.githubusercontent.com/250527/212925052-6afc1e8b-057a-4dc6-9f8b-24192f3a3fd1.gif)

christian-bromann: Suggestion on the UX: instead of having a checkbox and an input for mime type and name, just have a single input and only set the annotation if the input has a value. We could work with placeholders or labels to identify what the input stands for.
sourishkrout: Merging behind feature flag so you can use this as baseline for UX/UI stuff @degrammer.

Patch:
diff --git a/package-lock.json b/package-lock.json
index 90676779..c2d95e82 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -21,6 +21,7 @@
         "preact": "^10.11.3",
         "tmp-promise": "^3.0.3",
         "undici": "^5.14.0",
+        "uuid": "^9.0.0",
         "vercel": "^28.10.2",
         "xdg-app-paths": "^8.2.0",
         "yargs": "^17.6.2"
@@ -29,6 +30,7 @@
         "@octokit/rest": "^19.0.5",
         "@types/node": "^18.11.15",
         "@types/split2": "^3.2.1",
+        "@types/uuid": "^9.0.0",
         "@types/vscode": "^1.71.0",
         "@types/vscode-notebook-renderer": "^1.72.0",
         "@types/webpack-env": "^1.18.0",
@@ -1441,6 +1443,12 @@
       "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.2.tgz",
       "integrity": "sha512-F5DIZ36YVLE+PN+Zwws4kJogq47hNgX3Nx6WyDJ3kcplxyke3XIzB8uK5n/Lpm1HBsbGzd6nmGehL8cPekP+Tg=="
     },
+    "node_modules/@types/uuid": {
+      "version": "9.0.0",
+      "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-9.0.0.tgz",
+      "integrity": "sha512-kr90f+ERiQtKWMz5rP32ltJ/BtULDI5RVO0uavn1HQUOwjx0R1h0rnDYNL0CepF1zL5bSY6FISAfd9tOdDhU5Q==",
+      "dev": true
+    },
     "node_modules/@types/vscode": {
       "version": "1.74.0",
       "resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.74.0.tgz",
@@ -15156,7 +15164,6 @@
       "version": "9.0.0",
       "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz",
       "integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==",
-      "dev": true,
       "bin": {
         "uuid": "dist/bin/uuid"
       }
@@ -17349,6 +17356,12 @@
       "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.2.tgz",
       "integrity": "sha512-F5DIZ36YVLE+PN+Zwws4kJogq47hNgX3Nx6WyDJ3kcplxyke3XIzB8uK5n/Lpm1HBsbGzd6nmGehL8cPekP+Tg=="
     },
+    "@types/uuid": {
+      "version": "9.0.0",
+      "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-9.0.0.tgz",
+      "integrity": "sha512-kr90f+ERiQtKWMz5rP32ltJ/BtULDI5RVO0uavn1HQUOwjx0R1h0rnDYNL0CepF1zL5bSY6FISAfd9tOdDhU5Q==",
+      "dev": true
+    },
     "@types/vscode": {
       "version": "1.74.0",
       "resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.74.0.tgz",
@@ -27644,8 +27657,7 @@
     "uuid": {
       "version": "9.0.0",
       "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz",
-      "integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==",
-      "dev": true
+      "integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg=="
     },
     "v8-compile-cache-lib": {
       "version": "3.0.1",
diff --git a/package.json b/package.json
index 62543ee6..01ff640c 100644
--- a/package.json
+++ b/package.json
@@ -163,6 +163,7 @@
           "stateful.runme/deno-stdout",
           "stateful.runme/html-stdout",
           "stateful.runme/output-items",
+          "stateful.runme/annotations",
           "stateful.runme/error"
         ],
         "requiresMessaging": "optional"
@@ -195,6 +196,12 @@
             "scope": "machine",
             "default": false,
             "markdownDescription": "If set to `true`, you will executing code blocks natively through the Runme CLI (very experimental)."
+          },
+          "runme.experiments.annotationsEdit": {
+            "type": "boolean",
+            "scope": "machine",
+            "default": false,
+            "markdownDescription": "If set to `true`, a button per cell will allow you to drop into a annotations editor."
           }
         }
       }
@@ -276,6 +283,7 @@
     "@octokit/rest": "^19.0.5",
     "@types/node": "^18.11.15",
     "@types/split2": "^3.2.1",
+    "@types/uuid": "^9.0.0",
     "@types/vscode": "^1.71.0",
     "@types/vscode-notebook-renderer": "^1.72.0",
     "@types/webpack-env": "^1.18.0",
@@ -320,6 +328,7 @@
     "preact": "^10.11.3",
     "tmp-promise": "^3.0.3",
     "undici": "^5.14.0",
+    "uuid": "^9.0.0",
     "vercel": "^28.10.2",
     "xdg-app-paths": "^8.2.0",
     "yargs": "^17.6.2"
diff --git a/src/client/components/annotations.ts b/src/client/components/annotations.ts
new file mode 100644
index 00000000..7881bccc
--- /dev/null
+++ b/src/client/components/annotations.ts
@@ -0,0 +1,157 @@
+import { LitElement, css, html, nothing } from 'lit'
+import { customElement, property } from 'lit/decorators.js'
+import { when } from 'lit/directives/when.js'
+
+import { ClientMessages } from '../../constants'
+import type { ClientMessage, NotebookCellAnnotations } from '../../types'
+import { getContext } from '../utils'
+import '@vscode/webview-ui-toolkit/dist/data-grid/index'
+
+type AnnotationsMutation = Partial<NotebookCellAnnotations>
+type AnnotationsKey = keyof NotebookCellAnnotations
+
+@customElement('edit-annotations')
+export class Annotations extends LitElement {
+  // Define scoped styles right with your component, in plain CSS
+  static styles = css`
+    :host {
+      display: block;
+      font-family: Arial;
+    }
+
+    section {
+      padding: 10px;
+      border: 1px solid var(--vscode-input-border);
+      border-radius: 5px;
+      display: flex;
+      flex-direction: row;
+      gap: 50px;
+      align-items: flex-start;
+    }
+
+    img {
+      width: 100px;
+      padding: 20px;
+    }
+
+    h4 {
+      margin-bottom: 0;
+    }
+  `
+
+  // Declare reactive properties
+  @property({ type: Object, reflect: true })
+  annotations?: NotebookCellAnnotations
+
+  #onChange(e: { target: { id: AnnotationsKey, checked: boolean, value: string, type: string } }) {
+    if (!this.annotations || !e.target) {
+      return
+    }
+
+    const propVal: any = { 'runme.dev/uuid': this.annotations['runme.dev/uuid'] }
+    const propName = e.target.id
+    switch (e.target.type) {
+      case 'text':
+        (this.annotations as any)[propName] = e.target.value
+        propVal[propName] = e.target.value
+        return this.#dispatch(propVal)
+      default:
+        (this.annotations as any)[e.target.id] = e.target.checked.toString()
+        propVal[propName] = e.target.checked
+        return this.#dispatch(propVal)
+    }
+  }
+
+  #dispatch(prop: AnnotationsMutation) {
+    const ctx = getContext()
+    if (!ctx.postMessage) {
+      return
+    }
+
+    ctx.postMessage(<ClientMessage<ClientMessages.mutateAnnotations>>{
+      type: ClientMessages.mutateAnnotations,
+      output: { annotations: prop }
+    })
+  }
+
+  // Render the UI as a function of component state
+  render() {
+    // const supportsMessaging = Boolean(getContext().postMessage)
+    if (!this.annotations) {
+      return html`⚠️ Whoops! Something went wrong displaying the editing UI!`
+    }
+
+    const filtered = Object.entries(this.annotations).filter(
+      ([k]) => k.indexOf('runme.dev/') < 0
+    )
+
+    const headers = filtered
+      .map(([id, val], i) => {
+        return html`<vscode-data-grid-cell
+          cell-type="columnheader"
+          grid-column="${i + 1}"
+        >
+          ${when(
+            ['true', 'false'].includes(val.toString()),
+            () => {
+              return html`<vscode-checkbox
+                id="${id}"
+                @change="${this.#onChange}"
+                checked="${val || nothing}"
+              >
+                ${id}
+              </vscode-checkbox>`
+            },
+            () => {
+              return html`<vscode-checkbox
+                id="${id}"
+                @change="${this.#onChange}"
+                @blur="${this.#onChange}"
+                checked
+                readonly
+              >
+                ${id}</vscode-checkbox
+              >`
+            }
+          )}
+        </vscode-data-grid-cell>`
+      })
+
+    const annos = filtered.map(([key, val], i) => {
+      return html`<vscode-data-grid-cell grid-column="${i + 1}">
+        ${when(
+          !['true', 'false'].includes(val.toString()),
+          () => {
+            return html`<vscode-text-field
+              id="${key}"
+              type="text"
+              value="${val}"
+              @change="${this.#onChange}"
+            ></vscode-text-field>`
+          },
+          () => {
+            return html``
+          }
+        )}
+      </vscode-data-grid-cell>`
+    })
+
+    return html` <section id="data-grid-row">
+      <vscode-data-grid
+        class="basic-grid"
+        generate-header="default"
+        grid-template-columns="17% 17% 32% 17% 17%"
+        aria-label="Cell Annotations"
+      >
+        <vscode-data-grid-row row-type="header">
+          ${headers}
+        </vscode-data-grid-row>
+        <vscode-data-grid-row> ${annos} </vscode-data-grid-row>
+      </vscode-data-grid>
+    </section>`
+  }
+
+  #reset() {
+    throw new Error('not implemented yet')
+  }
+}
diff --git a/src/client/components/index.ts b/src/client/components/index.ts
index 416f7d97..9966622f 100644
--- a/src/client/components/index.ts
+++ b/src/client/components/index.ts
@@ -5,3 +5,4 @@ export * from './deno'
 export * from './vite'
 export * from './vercel'
 export * from './outputItems'
+export * from './annotations'
diff --git a/src/client/index.ts b/src/client/index.ts
index 7a912c41..9cc7f9cf 100644
--- a/src/client/index.ts
+++ b/src/client/index.ts
@@ -48,6 +48,11 @@ export const activate: ActivationFunction = (context: RendererContext<void>) =>
           outputItemElem.setAttribute('content', content)
           element.appendChild(outputItemElem)
           break
+        case OutputType.annotations:
+          const annoElem = document.createElement('edit-annotations')
+          annoElem.setAttribute('annotations', JSON.stringify(payload.output.annotations ?? []))
+          element.appendChild(annoElem)
+          break
         case OutputType.error:
           element.innerHTML = `⚠️ ${payload.output}`
           break
diff --git a/src/constants.ts b/src/constants.ts
index f0ed613d..8fb0247f 100644
--- a/src/constants.ts
+++ b/src/constants.ts
@@ -7,6 +7,7 @@ export enum OutputType {
   vercel = 'stateful.runme/vercel-stdout',
   deno = 'stateful.runme/deno-stdout',
   outputItems = 'stateful.runme/output-items',
+  annotations = 'stateful.runme/annotations',
   error = 'stateful.runme/error'
 }
 
@@ -16,7 +17,8 @@ export enum ClientMessages {
   update = 'deno:deploymentUpdate',
   deployed = 'deno:finishedDeployment',
   promote = 'deno:promoteDeployment',
-  prod = 'vercel:promotePreview'
+  prod = 'vercel:promotePreview',
+  mutateAnnotations = 'annotations:mutate'
 }
 
 export const METADATA_DEFAULTS = {
diff --git a/src/extension/commands/index.ts b/src/extension/commands/index.ts
index 7d8c1dce..f14a6b8b 100644
--- a/src/extension/commands/index.ts
+++ b/src/extension/commands/index.ts
@@ -7,7 +7,7 @@ import {
 
 import { Kernel } from '../kernel'
 import { CliProvider } from '../provider/cli'
-import { getMetadata, getTerminalByCell } from '../utils'
+import { getAnnotations, getTerminalByCell } from '../utils'
 
 function showWarningMessage () {
   return window.showWarningMessage('Couldn\'t find terminal! Was it already closed?')
@@ -44,14 +44,14 @@ export async function runCLICommand (cell: NotebookCell) {
       Uri.parse('https://github.com/stateful/runme/releases')
     ))
   }
-  const metadata = getMetadata(cell)
-  const term = window.createTerminal(`CLI: ${metadata.name}`)
+  const annotations = getAnnotations(cell)
+  const term = window.createTerminal(`CLI: ${annotations.name}`)
   const args = [
     `--chdir="${path.dirname(cell.document.uri.fsPath)}"`,
     `--filename="${path.basename(cell.document.uri.fsPath)}"`
   ]
   term.show(false)
-  term.sendText(`runme run ${metadata.name} ${args.join(' ')}`)
+  term.sendText(`runme run ${annotations.name} ${args.join(' ')}`)
 }
 export function openAsRunmeNotebook (doc: NotebookDocument) {
   window.showNotebookDocument(doc, {
diff --git a/src/extension/executors/runme.ts b/src/extension/executors/runme.ts
index 351ad137..bfe30978 100644
--- a/src/extension/executors/runme.ts
+++ b/src/extension/executors/runme.ts
@@ -4,7 +4,7 @@ import { NotebookCellOutput, NotebookCellOutputItem, NotebookCellExecution } fro
 
 import { RunmeTaskProvider } from '../provider/runmeTask'
 import { OutputType } from '../../constants'
-import { getMetadata } from '../utils'
+import { getAnnotations } from '../utils'
 import { ExperimentalTerminal } from '../terminal/terminal'
 import type { CellOutputPayload } from '../../types'
 import type { Kernel } from '../kernel'
@@ -18,27 +18,27 @@ export async function runme(
   terminal: ExperimentalTerminal
 ): Promise<boolean> {
   const outputItems: Buffer[] = []
-  const metadata = getMetadata(exec.cell)
+  const annotations = getAnnotations(exec.cell)
   const t = RunmeTaskProvider.getRunmeTask(
     exec.cell.notebook.uri.fsPath,
-    metadata.name,
+    annotations.name,
     {
-      isBackground: metadata.background,
-      closeTerminalOnSuccess: metadata.closeTerminalOnSuccess
+      isBackground: annotations.background,
+      closeTerminalOnSuccess: annotations.closeTerminalOnSuccess
     }
   )
 
   const outputStream = new PassThrough()
   outputStream.on('data', (data: Buffer) => {
     outputItems.push(Buffer.from(data))
-    let item = new NotebookCellOutputItem(Buffer.concat(outputItems), metadata.mimeType || DEFAULT_MIME_TYPE)
+    let item = new NotebookCellOutputItem(Buffer.concat(outputItems), annotations.mimeType || DEFAULT_MIME_TYPE)
 
-    if (MIME_TYPES_WITH_CUSTOM_RENDERERS.includes(metadata.mimeType)) {
+    if (MIME_TYPES_WITH_CUSTOM_RENDERERS.includes(annotations.mimeType)) {
       item = NotebookCellOutputItem.json(<CellOutputPayload<OutputType.outputItems>>{
         type: OutputType.outputItems,
         output: {
           content: Buffer.concat(outputItems).toString('base64'),
-          mime: metadata.mimeType
+          mime: annotations.mimeType
         }
       }, OutputType.outputItems)
     }
diff --git a/src/extension/executors/shell.ts b/src/extension/executors/shell.ts
index 498b24a7..f1805143 100644
--- a/src/extension/executors/shell.ts
+++ b/src/extension/executors/shell.ts
@@ -5,7 +5,7 @@ import { NotebookCellOutput, NotebookCellOutputItem, NotebookCellExecution } fro
 import { OutputType } from '../../constants'
 import type { CellOutputPayload } from '../../types'
 import type { Kernel } from '../kernel'
-import { getMetadata } from '../utils'
+import { getAnnotations } from '../utils'
 
 const MIME_TYPES_WITH_CUSTOM_RENDERERS = ['text/plain']
 
@@ -29,8 +29,8 @@ async function shellExecutor(
   /**
    * this needs more work / specification
    */
-  const metadata = getMetadata(exec.cell)
-  const mime = metadata?.mimeType || 'text/plain' as const
+  const annotations = getAnnotations(exec.cell)
+  const mime = annotations?.mimeType || 'text/plain' as const
   const index = exec.cell.index
 
   /**
diff --git a/src/extension/executors/task.ts b/src/extension/executors/task.ts
index 2cfe2397..755c7c72 100644
--- a/src/extension/executors/task.ts
+++ b/src/extension/executors/task.ts
@@ -7,7 +7,7 @@ import {
 } from 'vscode'
 
 // import { ExperimentalTerminal } from "../terminal"
-import { getCmdShellSeq, getMetadata } from '../utils'
+import { getCmdShellSeq, getAnnotations } from '../utils'
 import { PLATFORM_OS, ENV_STORE } from '../constants'
 import type { Kernel } from '../kernel'
 
@@ -57,7 +57,7 @@ async function taskExecutor(
    * run as non interactive shell script if set as configuration or annotated
    * in markdown section
    */
-  const isInteractive = getMetadata(exec.cell).interactive
+  const isInteractive = getAnnotations(exec.cell).interactive
   if (!isInteractive) {
     return inlineSh.call(this, exec, cmdLine, cwd, env)
   }
@@ -75,13 +75,13 @@ async function taskExecutor(
     //   return new ExperimentalTerminal(scriptFile.fsPath, { cwd, env })
     // })
   )
-  const metadata = getMetadata(exec.cell)
-  taskExecution.isBackground = metadata.background
+  const annotations = getAnnotations(exec.cell)
+  taskExecution.isBackground = annotations.background
   taskExecution.presentationOptions = {
     focus: true,
     // why doesn't this work with Slient?
-    reveal: metadata.background ? TaskRevealKind.Never : TaskRevealKind.Always,
-    panel: metadata.background ? TaskPanelKind.Dedicated : TaskPanelKind.Shared
+    reveal: annotations.background ? TaskRevealKind.Never : TaskRevealKind.Always,
+    panel: annotations.background ? TaskPanelKind.Dedicated : TaskPanelKind.Shared
   }
   const execution = await tasks.executeTask(taskExecution)
 
@@ -119,7 +119,7 @@ async function taskExecutor(
       /**
        * only close terminal if execution passed and desired by user
        */
-      if (e.exitCode === 0 && metadata.closeTerminalOnSuccess) {
+      if (e.exitCode === 0 && annotations.closeTerminalOnSuccess) {
         closeTerminalByEnvID(RUNME_ID)
       }
 
@@ -127,7 +127,7 @@ async function taskExecutor(
     })
   })
 
-  if (metadata.background) {
+  if (annotations.background) {
     const giveItTime = new Promise<boolean>(
       (resolve) => setTimeout(() => {
         closeTerminalByEnvID(RUNME_ID)
diff --git a/src/extension/extension.ts b/src/extension/extension.ts
index 3705024a..3204cc20 100644
--- a/src/extension/extension.ts
+++ b/src/extension/extension.ts
@@ -6,6 +6,7 @@ import { ShowTerminalProvider, BackgroundTaskProvider, StopBackgroundTaskProvide
 import { CopyProvider } from './provider/copy'
 import { resetEnv } from './utils'
 import { CliProvider } from './provider/cli'
+import { AnnotationsProvider } from './provider/annotations'
 import { RunmeTaskProvider } from './provider/runmeTask'
 import {
   openTerminal,
@@ -22,9 +23,10 @@ import { Serializer } from './serializer'
 export class RunmeExtension {
   async initialize(context: ExtensionContext) {
     const kernel = new Kernel(context)
+    const serializer = new Serializer(context)
     context.subscriptions.push(
       kernel,
-      workspace.registerNotebookSerializer('runme', new Serializer(context), {
+      workspace.registerNotebookSerializer('runme', serializer, {
         transientOutputs: true,
         transientCellMetadata: {
           inputCollapsed: true,
@@ -36,6 +38,8 @@ export class RunmeExtension {
       notebooks.registerNotebookCellStatusBarItemProvider('runme', new BackgroundTaskProvider()),
       notebooks.registerNotebookCellStatusBarItemProvider('runme', new CopyProvider()),
       notebooks.registerNotebookCellStatusBarItemProvider('runme', new StopBackgroundTaskProvider()),
+      notebooks.registerNotebookCellStatusBarItemProvider('runme', new AnnotationsProvider(kernel)),
+
       commands.registerCommand('runme.resetEnv', resetEnv),
       commands.registerCommand('runme.openTerminal', openTerminal),
       commands.registerCommand('runme.runCliCommand', runCLICommand),
diff --git a/src/extension/kernel.ts b/src/extension/kernel.ts
index 3c897476..1fb8d172 100644
--- a/src/extension/kernel.ts
+++ b/src/extension/kernel.ts
@@ -1,6 +1,6 @@
 import {
   Disposable, notebooks, window, workspace, ExtensionContext,
-  NotebookEditor, NotebookCell, NotebookCellKind
+  NotebookEditor, NotebookCell, NotebookCellKind, NotebookCellExecution, WorkspaceEdit, NotebookEdit
 } from 'vscode'
 
 import type { ClientMessage } from '../types'
@@ -10,7 +10,7 @@ import { API } from '../utils/deno/api'
 import executor, { runme } from './executors'
 import { ExperimentalTerminal } from './terminal/terminal'
 import { ENV_STORE, DENO_ACCESS_TOKEN_KEY } from './constants'
-import { resetEnv, getKey, getMetadata } from './utils'
+import { resetEnv, getKey, getAnnotations } from './utils'
 
 import './wasm/wasm_exec.js'
 
@@ -24,6 +24,8 @@ enum ConfirmationItems {
 export class Kernel implements Disposable {
   static readonly type = 'runme' as const
 
+  readonly hasAnnotationsEditExperimentEnabled: boolean
+
   #terminals = new Map<string, ExperimentalTerminal>
   #disposables: Disposable[] = []
   #controller = notebooks.createNotebookController(
@@ -34,6 +36,9 @@ export class Kernel implements Disposable {
   protected messaging = notebooks.createRendererMessaging('runme-renderer')
 
   constructor(protected context: ExtensionContext) {
+    const config = workspace.getConfiguration('runme.experiments')
+    this.hasAnnotationsEditExperimentEnabled = config.get<boolean>('annotationsEdit', false)
+
     this.#controller.supportedLanguages = Object.keys(executor)
     this.#controller.supportsExecutionOrder = false
     this.#controller.description = 'Run your README.md'
@@ -54,7 +59,41 @@ export class Kernel implements Disposable {
 
   // eslint-disable-next-line max-len
   async #handleRendererMessage({ editor, message }: { editor: NotebookEditor, message: ClientMessage<ClientMessages> }) {
-    if (message.type === ClientMessages.promote) {
+    if (message.type === ClientMessages.mutateAnnotations) {
+      const payload = message as ClientMessage<ClientMessages.mutateAnnotations>
+
+      let editCell: NotebookCell | undefined = undefined
+      for (const document of workspace.notebookDocuments) {
+        for (const cell of document.getCells()) {
+          if (cell.document.uri.fsPath !== editor.notebook.uri.fsPath) {
+            break
+          }
+
+          if (cell.metadata?.['runme.dev/uuid'] === payload.output.annotations['runme.dev/uuid']) {
+            editCell = cell
+            break
+          }
+        }
+
+        if (editCell) {
+          break
+        }
+		}
+
+      if (editCell) {
+        const edit = new WorkspaceEdit()
+        const newMetadata = {
+          ...editCell.metadata,
+          ...payload.output.annotations,
+        }
+        const notebookEdit = NotebookEdit.updateCellMetadata(editCell.index, newMetadata)
+
+        edit.set(editCell.notebook.uri, [notebookEdit])
+        await workspace.applyEdit(edit)
+      }
+
+      return
+    } else if (message.type === ClientMessages.promote) {
       const payload = message as ClientMessage<ClientMessages.promote>
       const token = ENV_STORE.get(DENO_ACCESS_TOKEN_KEY)
       if (!token) {
@@ -81,7 +120,7 @@ export class Kernel implements Disposable {
       return window.showInformationMessage(message.output as string)
     }
 
-    console.error(`[Runme] Unknown event type: ${message.type}`)
+    console.error(`[Runme] Unknown kernel event type: ${message.type}`)
   }
 
   private async _executeAll(cells: NotebookCell[]) {
@@ -94,10 +133,10 @@ export class Kernel implements Disposable {
 
     for (const cell of cells) {
       if (showConfirmPrompt) {
-        const metadata = getMetadata(cell)
+        const annotations = getAnnotations(cell)
         const cellText = cell.document.getText()
         const cellLabel = (
-          metadata.name ||
+          annotations.name ||
           cellText.length > 20 ? `${cellText.slice(0, 20)}...` : cellText
         )
 
@@ -123,9 +162,13 @@ export class Kernel implements Disposable {
     }
   }
 
+  public async createCellExecution(cell: NotebookCell): Promise<NotebookCellExecution> {
+    return this.#controller.createNotebookCellExecution(cell)
+  }
+
   private async _doExecuteCell(cell: NotebookCell): Promise<void> {
     const runningCell = await workspace.openTextDocument(cell.document.uri)
-    const exec = this.#controller.createNotebookCellExecution(cell)
+    const exec = await this.createCellExecution(cell)
 
     exec.start(Date.now())
     let execKey = getKey(runningCell)
diff --git a/src/extension/provider/annotations.ts b/src/extension/provider/annotations.ts
new file mode 100644
index 00000000..1de6e459
--- /dev/null
+++ b/src/extension/provider/annotations.ts
@@ -0,0 +1,64 @@
+import {
+  window,
+  commands,
+  NotebookCell,
+  NotebookCellOutput,
+  NotebookCellOutputItem,
+  NotebookCellStatusBarItemProvider,
+  NotebookCellStatusBarItem,
+  NotebookCellStatusBarAlignment,
+} from 'vscode'
+
+import { OutputType } from '../../constants'
+import { CellOutputPayload } from '../../types'
+import { Kernel } from '../kernel'
+import { getAnnotations } from '../utils'
+
+export class AnnotationsProvider implements NotebookCellStatusBarItemProvider {
+  constructor(private readonly kernel: Kernel) {
+    commands.registerCommand(
+      'runme.openCellAnnotations',
+      async (cell: NotebookCell) => {
+        try {
+          const exec = await kernel.createCellExecution(cell)
+          exec.start(Date.now())
+          const json = <CellOutputPayload<OutputType.annotations>>{
+            type: OutputType.annotations,
+            output: {
+              annotations: getAnnotations(cell),
+            },
+          }
+          await exec.replaceOutput([
+            new NotebookCellOutput([
+              NotebookCellOutputItem.json(json, OutputType.annotations),
+              NotebookCellOutputItem.json(json),
+            ]),
+          ])
+          exec.end(true)
+        } catch (e: any) {
+          window.showErrorMessage(e.message)
+        }
+      }
+    )
+  }
+
+  async provideCellStatusBarItems(
+    cell: NotebookCell
+  ): Promise<NotebookCellStatusBarItem | undefined> {
+    if (!this.kernel.hasAnnotationsEditExperimentEnabled) {
+      return
+    }
+
+    const item = new NotebookCellStatusBarItem(
+      '$(output-view-icon) Annotations',
+      NotebookCellStatusBarAlignment.Right
+    )
+
+    item.command = {
+      title: 'Edit cell annotations',
+      command: 'runme.openCellAnnotations',
+      arguments: [cell],
+    }
+    return item
+  }
+}
diff --git a/src/extension/provider/background.ts b/src/extension/provider/background.ts
index 99837790..4736573e 100644
--- a/src/extension/provider/background.ts
+++ b/src/extension/provider/background.ts
@@ -1,13 +1,13 @@
 import vscode from 'vscode'
 
-import { getMetadata, getTerminalByCell } from '../utils'
+import { getAnnotations, getTerminalByCell } from '../utils'
 
 export class ShowTerminalProvider implements vscode.NotebookCellStatusBarItemProvider {
   async provideCellStatusBarItems(cell: vscode.NotebookCell): Promise<vscode.NotebookCellStatusBarItem | undefined> {
     /**
      * don't show status item if we run it in non-interactive mode where there is no terminal to open
      */
-    if (!getMetadata(cell).interactive) {
+    if (!getAnnotations(cell).interactive) {
       return
     }
 
@@ -29,12 +29,12 @@ export class ShowTerminalProvider implements vscode.NotebookCellStatusBarItemPro
 
 export class BackgroundTaskProvider implements vscode.NotebookCellStatusBarItemProvider {
   provideCellStatusBarItems(cell: vscode.NotebookCell): vscode.NotebookCellStatusBarItem | undefined {
-    const metadata = getMetadata(cell)
+    const annotations = getAnnotations(cell)
 
     /**
      * don't show if not a background task
      */
-    if (!metadata.background || !metadata.interactive) {
+    if (!annotations.background || !annotations.interactive) {
       return
     }
 
@@ -47,12 +47,12 @@ export class BackgroundTaskProvider implements vscode.NotebookCellStatusBarItemP
 }
 export class StopBackgroundTaskProvider implements vscode.NotebookCellStatusBarItemProvider {
   provideCellStatusBarItems(cell: vscode.NotebookCell): vscode.NotebookCellStatusBarItem | undefined {
-    const metadata = getMetadata(cell)
+    const annotations = getAnnotations(cell)
 
     /**
      * don't show if not a background task & if not command currently running
      */
-    if (!metadata.background || !metadata.interactive || !cell.executionSummary?.success) {
+    if (!annotations.background || !annotations.interactive || !cell.executionSummary?.success) {
       return
     }
 
diff --git a/src/extension/provider/cli.ts b/src/extension/provider/cli.ts
index 7877ddff..78c804b7 100644
--- a/src/extension/provider/cli.ts
+++ b/src/extension/provider/cli.ts
@@ -2,15 +2,15 @@ import { spawn } from 'node:child_process'
 
 import vscode from 'vscode'
 
-import { getMetadata } from '../utils'
+import { getAnnotations } from '../utils'
 
 export class CliProvider implements vscode.NotebookCellStatusBarItemProvider {
   async provideCellStatusBarItems(cell: vscode.NotebookCell): Promise<vscode.NotebookCellStatusBarItem | undefined> {
     /**
      * only show CLI if runme.dev/name is known
      */
-    const metadata = getMetadata(cell)
-    if (!metadata.name) {
+    const annotations = getAnnotations(cell)
+    if (!annotations.name) {
       return
     }
 
diff --git a/src/extension/provider/pid.ts b/src/extension/provider/pid.ts
index d5ca27d3..638f3cea 100644
--- a/src/extension/provider/pid.ts
+++ b/src/extension/provider/pid.ts
@@ -1,6 +1,6 @@
 import vscode from 'vscode'
 
-import { getMetadata, getTerminalByCell } from '../utils'
+import { getAnnotations, getTerminalByCell } from '../utils'
 
 export class PidStatusProvider implements vscode.NotebookCellStatusBarItemProvider {
   async provideCellStatusBarItems(cell: vscode.NotebookCell): Promise<vscode.NotebookCellStatusBarItem | undefined> {
@@ -8,7 +8,7 @@ export class PidStatusProvider implements vscode.NotebookCellStatusBarItemProvid
      * don't show pid if we run it in non-interactive mode where we have no
      * access to the process id
      */
-    if (!getMetadata(cell).interactive) {
+    if (!getAnnotations(cell).interactive) {
       return
     }
 
diff --git a/src/extension/provider/runmeTask.ts b/src/extension/provider/runmeTask.ts
index e31c27a4..1fe7d391 100644
--- a/src/extension/provider/runmeTask.ts
+++ b/src/extension/provider/runmeTask.ts
@@ -5,7 +5,7 @@ import {
   workspace, window, TaskScope, TaskRevealKind, TaskPanelKind, NotebookCellKind
 } from 'vscode'
 
-import { initWasm, getMetadata } from '../utils'
+import { initWasm, getAnnotations } from '../utils'
 import { WasmLib, RunmeTaskDefinition } from '../../types'
 
 declare var globalThis: any
@@ -52,7 +52,7 @@ export class RunmeTaskProvider implements TaskProvider {
         current.fsPath,
         // ToDo(Christian) remove casting once we have defaults
         // see https://github.com/stateful/runme/issues/90
-        getMetadata(cell as any).name
+        getAnnotations(cell as any).name
       ))
   }
 
diff --git a/src/extension/serializer.ts b/src/extension/serializer.ts
index 9ee7cc62..e988360c 100644
--- a/src/extension/serializer.ts
+++ b/src/extension/serializer.ts
@@ -1,4 +1,3 @@
-/* eslint-disable @typescript-eslint/no-unused-vars */
 import {
   window,
   commands,
@@ -10,6 +9,7 @@ import {
   NotebookCellKind,
   CancellationToken,
 } from 'vscode'
+import { v4 as uuidv4 } from 'uuid'
 
 import { WasmLib } from '../types'
 
@@ -17,6 +17,7 @@ import Languages from './languages'
 import { PLATFORM_OS } from './constants'
 import { canEditFile, initWasm } from './utils'
 
+
 declare var globalThis: any
 const DEFAULT_LANG_ID = 'text'
 
@@ -26,32 +27,42 @@ export class Serializer implements NotebookSerializer {
 
   constructor(private context: ExtensionContext) {
     this.languages = Languages.fromContext(this.context)
-
-    const wasmUri = Uri.joinPath(this.context.extensionUri, 'wasm', 'runme.wasm')
+    const wasmUri = Uri.joinPath(
+      this.context.extensionUri,
+      'wasm',
+      'runme.wasm'
+    )
     this.wasmReady = initWasm(wasmUri)
   }
 
   public async serializeNotebook(
     data: NotebookData,
+    // eslint-disable-next-line @typescript-eslint/no-unused-vars
     token: CancellationToken
   ): Promise<Uint8Array> {
     if (!window.activeNotebookEditor) {
       throw new Error('Could\'t save notebook as it is not active!')
     }
 
-    if (!await canEditFile(window.activeNotebookEditor.notebook)) {
-      const errorMessage = (
+    if (!(await canEditFile(window.activeNotebookEditor.notebook))) {
+      const errorMessage =
         'You are writing to a file that is not version controlled! ' +
         'Runme\'s authoring features are in early stages and require hardening. ' +
         'We wouldn\'t want you to loose important data. Please version track your file first ' +
         'or disable this restriction in the VS Code settings.'
+      window
+        .showErrorMessage(errorMessage, 'Open Runme Settings')
+        .then((openSettings) => {
+          if (openSettings) {
+            return commands.executeCommand(
+              'workbench.action.openSettings',
+              'runme.flags.disableSaveRestriction'
+            )
+          }
+        })
+      throw new Error(
+        'saving non version controlled notebooks is disabled by default.'
       )
-      window.showErrorMessage(errorMessage, 'Open Runme Settings').then((openSettings) => {
-        if (openSettings) {
-          return commands.executeCommand('workbench.action.openSettings', 'runme.flags.disableSaveRestriction')
-        }
-      })
-      throw new Error('saving non version controlled notebooks is disabled by default.')
     }
 
     const err = await this.wasmReady
@@ -72,6 +83,7 @@ export class Serializer implements NotebookSerializer {
 
   public async deserializeNotebook(
     content: Uint8Array,
+    // eslint-disable-next-line @typescript-eslint/no-unused-vars
     token: CancellationToken
   ): Promise<NotebookData> {
     let notebook: WasmLib.Notebook
@@ -117,8 +129,9 @@ export class Serializer implements NotebookSerializer {
       console.error(`Error guessing snippet languages: ${err}`)
     }
 
-    const cells = Serializer.revive(notebook)
-    const notebookData = new NotebookData(cells)
+    const notebookData = new NotebookData(Serializer.revive(notebook))
+    // const cellAnnotations = notebookData.cells.map(c => getAnnotations(c.metadata))
+    // this.#annotationsEditor.reset(notebookData)
     if (notebook.metadata) {
       notebookData.metadata = notebook.metadata
     }
@@ -144,6 +157,9 @@ export class Serializer implements NotebookSerializer {
       }
 
       cell.metadata = { ...elem.metadata }
+      // todo(sebastian): decide if the serializer should own lifecycle
+      cell.metadata['runme.dev/uuid'] = uuidv4()
+
       accu.push(cell)
 
       return accu
@@ -152,7 +168,9 @@ export class Serializer implements NotebookSerializer {
 
   public static normalize(source: string): string {
     const lines = source.split('\n')
-    const normed = lines.filter(l => !(l.trim().startsWith('```') || l.trim().endsWith('```')))
+    const normed = lines.filter(
+      (l) => !(l.trim().startsWith('```') || l.trim().endsWith('```'))
+    )
     return normed.join('\n')
   }
 
diff --git a/src/extension/utils.ts b/src/extension/utils.ts
index 6c2f110a..27e512e0 100644
--- a/src/extension/utils.ts
+++ b/src/extension/utils.ts
@@ -5,7 +5,7 @@ import cp from 'node:child_process'
 import vscode, { FileType, Uri, workspace, NotebookDocument } from 'vscode'
 
 import { METADATA_DEFAULTS } from '../constants'
-import { NotebookCellMetadata } from '../types'
+import { NotebookCellAnnotations, WasmLib } from '../types'
 
 import executor from './executors'
 import { Kernel } from './kernel'
@@ -16,22 +16,44 @@ declare var globalThis: any
 const HASH_PREFIX_REGEXP = /^\s*\#\s*/g
 const TRUTHY_VALUES = ['1', 'true']
 
-export function getMetadata(cell: vscode.NotebookCell) {
+/**
+ * Annotations are stored as subset of metadata
+ */
+export function getAnnotations(cell: vscode.NotebookCell): NotebookCellAnnotations
+export function getAnnotations(metadata?: WasmLib.Metadata): NotebookCellAnnotations
+export function getAnnotations(raw: unknown): NotebookCellAnnotations {
   const config = vscode.workspace.getConfiguration('runme.shell')
-  return <NotebookCellMetadata>{
-    background: typeof cell.metadata.background === 'string'
-      ? TRUTHY_VALUES.includes(cell.metadata.background)
-      : METADATA_DEFAULTS.background,
-    interactive: typeof cell.metadata.interactive === 'string'
-      ? TRUTHY_VALUES.includes(cell.metadata.interactive)
-      : config.get<boolean>('interactive', METADATA_DEFAULTS.interactive),
-    closeTerminalOnSuccess: typeof cell.metadata.closeTerminalOnSuccess === 'string'
-      ? TRUTHY_VALUES.includes(cell.metadata.closeTerminalOnSuccess)
-      : config.get<boolean>('closeTerminalOnSuccess', METADATA_DEFAULTS.closeTerminalOnSuccess),
-    mimeType: typeof cell.metadata.mimeType === 'string'
-      ? cell.metadata.mimeType
-      : METADATA_DEFAULTS.mimeType,
-    name: cell.metadata['runme.dev/name'] || `Cell #${Math.random().toString().slice(2)}`
+  const metadataFromCell = raw as vscode.NotebookCell
+  let metadata = raw as WasmLib.Metadata
+
+  if (metadataFromCell.metadata) {
+    metadata = metadataFromCell.metadata
+  }
+
+  return <NotebookCellAnnotations>{
+    background:
+      typeof metadata.background === 'string'
+        ? TRUTHY_VALUES.includes(metadata.background)
+        : METADATA_DEFAULTS.background,
+    interactive:
+      typeof metadata.interactive === 'string'
+        ? TRUTHY_VALUES.includes(metadata.interactive)
+        : config.get<boolean>('interactive', METADATA_DEFAULTS.interactive),
+    closeTerminalOnSuccess:
+      typeof metadata.closeTerminalOnSuccess === 'string'
+        ? TRUTHY_VALUES.includes(metadata.closeTerminalOnSuccess)
+        : config.get<boolean>(
+            'closeTerminalOnSuccess',
+            METADATA_DEFAULTS.closeTerminalOnSuccess
+          ),
+    mimeType:
+      typeof metadata.mimeType === 'string'
+        ? metadata.mimeType
+        : METADATA_DEFAULTS.mimeType,
+    name:
+      metadata['runme.dev/name'] ||
+      `Cell #${Math.random().toString().slice(2)}`,
+    'runme.dev/uuid': metadata['runme.dev/uuid'],
   }
 }
 
diff --git a/src/types.ts b/src/types.ts
index 2f22686a..7fefbabd 100644
--- a/src/types.ts
+++ b/src/types.ts
@@ -33,6 +33,7 @@ export namespace WasmLib {
     closeTerminalOnSuccess?: string
     mimeType?: string
     ['runme.dev/name']?: string
+    ['runme.dev/uuid']?: string
   }
 }
 
@@ -62,6 +63,9 @@ interface Payload {
     outputItems: string[]
   }
   [OutputType.outputItems]: OutputItemsPayload
+  [OutputType.annotations]: {
+    annotations?: NotebookCellAnnotations
+  }
 }
 
 export interface ClientMessage <T extends ClientMessages> {
@@ -78,6 +82,9 @@ export interface ClientMessagePayload {
   [ClientMessages.prod]: {
     cellIndex: number
   }
+  [ClientMessages.mutateAnnotations]: {
+    annotations: NotebookCellAnnotations
+  }
   [ClientMessages.infoMessage]: string
   [ClientMessages.errorMessage]: string
 }
@@ -96,7 +103,8 @@ export interface RunmeTaskDefinition {
   cwd?: string
 }
 
-export interface NotebookCellMetadata {
+export interface NotebookCellAnnotations {
+  'runme.dev/uuid': string
   background: boolean
   interactive: boolean
   closeTerminalOnSuccess: boolean


Test patch:
diff --git a/tests/extension/commands/index.test.ts b/tests/extension/commands/index.test.ts
index fc56f460..8468c3a8 100644
--- a/tests/extension/commands/index.test.ts
+++ b/tests/extension/commands/index.test.ts
@@ -23,12 +23,12 @@ import {
   stopBackgroundTask,
   createNewRunmeNotebook
 } from '../../../src/extension/commands'
-import { getTerminalByCell, getMetadata } from '../../../src/extension/utils'
+import { getTerminalByCell, getAnnotations } from '../../../src/extension/utils'
 import { CliProvider } from '../../../src/extension/provider/cli'
 
 vi.mock('vscode', () => import(path.join(process.cwd(), '__mocks__', 'vscode')))
 vi.mock('../../../src/extension/utils', () => ({
-  getMetadata: vi.fn(),
+  getAnnotations: vi.fn(),
   getTerminalByCell: vi.fn()
 }))
 vi.mock('../../../src/extension/provider/cli', () => ({
@@ -40,7 +40,7 @@ vi.mock('../../../src/extension/provider/cli', () => ({
 beforeEach(() => {
   vi.mocked(window.showWarningMessage).mockClear()
   vi.mocked(window.showInformationMessage).mockClear()
-  vi.mocked(getMetadata).mockClear()
+  vi.mocked(getAnnotations).mockClear()
 })
 
 test('openTerminal', () => {
@@ -74,7 +74,7 @@ test('runCLICommand if CLI is installed', async () => {
     document: { uri: { fsPath: '/foo/bar/README.md' }}
   }
   vi.mocked(CliProvider.isCliInstalled).mockResolvedValue(true)
-  vi.mocked(getMetadata).mockReturnValue(cell.metadata)
+  vi.mocked(getAnnotations).mockReturnValue(cell.metadata)
   await runCLICommand(cell)
   expect(window.createTerminal).toBeCalledWith('CLI: foobar')
   expect(terminal.show).toBeCalledTimes(1)
diff --git a/tests/extension/extension.test.ts b/tests/extension/extension.test.ts
index eef024f3..566e16d0 100644
--- a/tests/extension/extension.test.ts
+++ b/tests/extension/extension.test.ts
@@ -9,7 +9,7 @@ test('initializes all providers', async () => {
   const context: any = { subscriptions: [], extensionUri: { fsPath: '/foo/bar' } }
   const ext = new RunmeExtension()
   await ext.initialize(context)
-  expect(notebooks.registerNotebookCellStatusBarItemProvider).toBeCalledTimes(5)
+  expect(notebooks.registerNotebookCellStatusBarItemProvider).toBeCalledTimes(6)
   expect(workspace.registerNotebookSerializer).toBeCalledTimes(1)
-  expect(commands.registerCommand).toBeCalledTimes(8)
+  expect(commands.registerCommand).toBeCalledTimes(9)
 })
diff --git a/tests/extension/kernel.test.ts b/tests/extension/kernel.test.ts
index 777a14a4..31410d1c 100644
--- a/tests/extension/kernel.test.ts
+++ b/tests/extension/kernel.test.ts
@@ -10,7 +10,7 @@ vi.mock('vscode')
 vi.mock('../../src/extension/utils', () => ({
   resetEnv: vi.fn(),
   getKey: vi.fn().mockReturnValue('foobar'),
-  getMetadata: vi.fn((cell) => cell.metadata),
+  getAnnotations: vi.fn((cell) => cell.metadata),
 }))
 vi.mock('../../src/extension/executors/index.js', () => ({
   default: { foobar: vi.fn() }
diff --git a/tests/extension/provider/background.test.ts b/tests/extension/provider/background.test.ts
index f7b9583d..d6d5af7b 100644
--- a/tests/extension/provider/background.test.ts
+++ b/tests/extension/provider/background.test.ts
@@ -1,6 +1,6 @@
 import { vi, describe, expect, beforeEach, it } from 'vitest'
 
-import { getMetadata, getTerminalByCell } from '../../../src/extension/utils'
+import { getAnnotations, getTerminalByCell } from '../../../src/extension/utils'
 import {
   ShowTerminalProvider,
   BackgroundTaskProvider,
@@ -20,26 +20,26 @@ vi.mock('vscode', () => ({
 
 vi.mock('../../../src/extension/utils', () => ({
   getTerminalByCell: vi.fn(),
-  getMetadata: vi.fn()
+  getAnnotations: vi.fn()
 }))
 
 describe('ShowTerminalProvider', () => {
   beforeEach(() => {
     vi.mocked(getTerminalByCell).mockClear()
-    vi.mocked(getMetadata).mockClear()
+    vi.mocked(getAnnotations).mockClear()
   })
 
   it('dont show pid if cell is non interactive', async () => {
-    vi.mocked(getMetadata).mockReturnValueOnce({ interactive: false } as any)
+    vi.mocked(getAnnotations).mockReturnValueOnce({ interactive: false } as any)
     const p = new ShowTerminalProvider()
     expect(await p.provideCellStatusBarItems('cell' as any)).toBe(undefined)
-    expect(getMetadata).toBeCalledTimes(1)
+    expect(getAnnotations).toBeCalledTimes(1)
     expect(getTerminalByCell).toBeCalledTimes(0)
-    expect(getMetadata).toBeCalledWith('cell')
+    expect(getAnnotations).toBeCalledWith('cell')
   })
 
   it('dont show pid if terminal could not be found', async () => {
-    vi.mocked(getMetadata).mockReturnValueOnce({ interactive: true } as any)
+    vi.mocked(getAnnotations).mockReturnValueOnce({ interactive: true } as any)
     vi.mocked(getTerminalByCell).mockReturnValueOnce(undefined)
     const p = new ShowTerminalProvider()
     expect(await p.provideCellStatusBarItems('cell' as any)).toBe(undefined)
@@ -48,7 +48,7 @@ describe('ShowTerminalProvider', () => {
   })
 
   it('return status item with pid ', async () => {
-    vi.mocked(getMetadata).mockReturnValueOnce({ interactive: true } as any)
+    vi.mocked(getAnnotations).mockReturnValueOnce({ interactive: true } as any)
     vi.mocked(getTerminalByCell).mockReturnValueOnce({ processId: Promise.resolve(123) } as any)
     const p = new ShowTerminalProvider()
     const item = await p.provideCellStatusBarItems('cell' as any)
@@ -67,28 +67,28 @@ describe('BackgroundTaskProvider', () => {
 
   beforeEach(() => {
     vi.mocked(getTerminalByCell).mockClear()
-    vi.mocked(getMetadata).mockClear()
+    vi.mocked(getAnnotations).mockClear()
   })
 
   it('dont show bg task label if cell is non a background task', async () => {
-    vi.mocked(getMetadata).mockReturnValue({ background: false } as any)
+    vi.mocked(getAnnotations).mockReturnValue({ background: false } as any)
     const p = new BackgroundTaskProvider()
     expect(await p.provideCellStatusBarItems(cell as any)).toBe(undefined)
-    expect(getMetadata).toBeCalledTimes(1)
+    expect(getAnnotations).toBeCalledTimes(1)
   })
 
   it('dont show bg task label if cell is non interactive', async () => {
     cell.metadata.background = 'true'
     cell.metadata.interactive = false
-    vi.mocked(getMetadata).mockReturnValue(cell.metadata)
+    vi.mocked(getAnnotations).mockReturnValue(cell.metadata)
     const p = new BackgroundTaskProvider()
     expect(await p.provideCellStatusBarItems(cell as any)).toBe(undefined)
-    expect(getMetadata).toBeCalledTimes(1)
-    expect(getMetadata).toBeCalledWith(cell)
+    expect(getAnnotations).toBeCalledTimes(1)
+    expect(getAnnotations).toBeCalledWith(cell)
   })
 
   it('return status item with pid ', async () => {
-    vi.mocked(getMetadata).mockReturnValueOnce({
+    vi.mocked(getAnnotations).mockReturnValueOnce({
       interactive: true,
       background: true
     } as any)
@@ -109,18 +109,18 @@ describe('StopBackgroundTaskProvider', () => {
 
   beforeEach(() => {
     vi.mocked(getTerminalByCell).mockClear()
-    vi.mocked(getMetadata).mockClear()
+    vi.mocked(getAnnotations).mockClear()
   })
 
   it('dont show bg task label if cell is non a background task', async () => {
-    vi.mocked(getMetadata).mockReturnValue(cell.metadata)
+    vi.mocked(getAnnotations).mockReturnValue(cell.metadata)
     const p = new StopBackgroundTaskProvider()
     expect(await p.provideCellStatusBarItems(cell as any)).toBe(undefined)
   })
 
   it('dont show bg task label if cell is non interactive', async () => {
     cell.metadata.background = 'true'
-    vi.mocked(getMetadata).mockReturnValueOnce({ interactive: false } as any)
+    vi.mocked(getAnnotations).mockReturnValueOnce({ interactive: false } as any)
     const p = new StopBackgroundTaskProvider()
     expect(await p.provideCellStatusBarItems(cell as any)).toBe(undefined)
   })
@@ -128,7 +128,7 @@ describe('StopBackgroundTaskProvider', () => {
   it('dont show if cell was not yet executed', async () => {
     cell.metadata.background = 'true'
     cell.executionSummary.success = false
-    vi.mocked(getMetadata).mockReturnValueOnce({
+    vi.mocked(getAnnotations).mockReturnValueOnce({
       background: 'true'
     } as any)
     const p = new StopBackgroundTaskProvider()
@@ -139,7 +139,7 @@ describe('StopBackgroundTaskProvider', () => {
   it('return with button to close', async () => {
     cell.metadata.background = 'true'
     cell.executionSummary.success = true
-    vi.mocked(getMetadata).mockReturnValueOnce({
+    vi.mocked(getAnnotations).mockReturnValueOnce({
       interactive: true,
       background: 'true'
     } as any)
diff --git a/tests/extension/provider/pid.test.ts b/tests/extension/provider/pid.test.ts
index 7933dea2..194b01e4 100644
--- a/tests/extension/provider/pid.test.ts
+++ b/tests/extension/provider/pid.test.ts
@@ -1,6 +1,6 @@
 import { vi, test, expect, beforeEach } from 'vitest'
 
-import { getMetadata, getTerminalByCell } from '../../../src/extension/utils'
+import { getAnnotations, getTerminalByCell } from '../../../src/extension/utils'
 import { PidStatusProvider } from '../../../src/extension/provider/pid'
 
 vi.mock('vscode', () => ({
@@ -15,25 +15,25 @@ vi.mock('vscode', () => ({
 }))
 
 vi.mock('../../../src/extension/utils', () => ({
-  getMetadata: vi.fn(),
+  getAnnotations: vi.fn(),
   getTerminalByCell: vi.fn()
 }))
 
 beforeEach(() => {
-  vi.mocked(getMetadata).mockClear()
+  vi.mocked(getAnnotations).mockClear()
   vi.mocked(getTerminalByCell).mockClear()
 })
 
 test('dont show pid if cell is non interactive', async () => {
-  vi.mocked(getMetadata).mockReturnValueOnce({ interactive: false } as any)
+  vi.mocked(getAnnotations).mockReturnValueOnce({ interactive: false } as any)
   const p = new PidStatusProvider()
   expect(await p.provideCellStatusBarItems('cell' as any)).toBe(undefined)
-  expect(getMetadata).toBeCalledTimes(1)
+  expect(getAnnotations).toBeCalledTimes(1)
   expect(getTerminalByCell).toBeCalledTimes(0)
 })
 
 test('dont show pid if terminal could not be found', async () => {
-  vi.mocked(getMetadata).mockReturnValueOnce({ interactive: true } as any)
+  vi.mocked(getAnnotations).mockReturnValueOnce({ interactive: true } as any)
   vi.mocked(getTerminalByCell).mockReturnValueOnce(undefined)
   const p = new PidStatusProvider()
   expect(await p.provideCellStatusBarItems('cell' as any)).toBe(undefined)
@@ -42,14 +42,14 @@ test('dont show pid if terminal could not be found', async () => {
 })
 
 test('don\'t show if terminal has no pid', async () => {
-  vi.mocked(getMetadata).mockReturnValueOnce({ interactive: true } as any)
+  vi.mocked(getAnnotations).mockReturnValueOnce({ interactive: true } as any)
   vi.mocked(getTerminalByCell).mockReturnValueOnce({} as any)
   const p = new PidStatusProvider()
   expect(await p.provideCellStatusBarItems('cell' as any)).toBe(undefined)
 })
 
 test('return status item with pid ', async () => {
-  vi.mocked(getMetadata).mockReturnValueOnce({ interactive: true } as any)
+  vi.mocked(getAnnotations).mockReturnValueOnce({ interactive: true } as any)
   vi.mocked(getTerminalByCell).mockReturnValueOnce({ processId: Promise.resolve(123) } as any)
   const p = new PidStatusProvider()
   const item = await p.provideCellStatusBarItems('cell' as any)
diff --git a/tests/extension/utilts.test.ts b/tests/extension/utilts.test.ts
index 672a92c2..e4dd0c9f 100644
--- a/tests/extension/utilts.test.ts
+++ b/tests/extension/utilts.test.ts
@@ -8,7 +8,7 @@ import {
   getCmdShellSeq,
   normalizeLanguage,
   canEditFile,
-  getMetadata,
+  getAnnotations,
 } from '../../src/extension/utils'
 import { ENV_STORE, DEFAULT_ENV } from '../../src/extension/constants'
 
@@ -36,13 +36,13 @@ afterAll(() => { process.env.PATH = PATH })
 test('isInteractive', () => {
   // when set to false in configutaration
   vi.mocked(vscode.workspace.getConfiguration).mockReturnValue({ get: vi.fn().mockReturnValue(false) } as any)
-  expect(getMetadata({ metadata: {} } as any).interactive).toBe(false)
-  expect(getMetadata({ metadata: {} } as any).interactive).toBe(false)
-  expect(getMetadata({ metadata: { interactive: 'true' } } as any).interactive).toBe(true)
+  expect(getAnnotations({ metadata: {} } as any).interactive).toBe(false)
+  expect(getAnnotations({ metadata: {} } as any).interactive).toBe(false)
+  expect(getAnnotations({ metadata: { interactive: 'true' } } as any).interactive).toBe(true)
 
   vi.mocked(vscode.workspace.getConfiguration).mockReturnValue({ get: vi.fn().mockReturnValue(true) } as any)
-  expect(getMetadata({ metadata: {} } as any).interactive).toBe(true)
-  expect(getMetadata({ metadata: {} } as any).interactive).toBe(true)
+  expect(getAnnotations({ metadata: {} } as any).interactive).toBe(true)
+  expect(getAnnotations({ metadata: {} } as any).interactive).toBe(true)
 })
 
 test('getTerminalByCell', () => {