Skip to content

feat(sessions): Copy as Markdown — export a session, scrubbed - #65

Merged
ndemianc merged 2 commits into
developfrom
feat/session-export
Aug 15, 2026
Merged

feat(sessions): Copy as Markdown — export a session, scrubbed#65
ndemianc merged 2 commits into
developfrom
feat/session-export

Conversation

@ndemianc

Copy link
Copy Markdown
Contributor

The M4 export slice (levelcode-sessions-experience.md §6): one action turns a session into a clean Markdown transcript for pasting into a PR — and the seed of a later share-a-run.

# Idempotent refunds via Redis keys

_LevelCode session · 2026-07-28 · 2 turns · `anthropic/claude-opus-5` · `refund.rb`, `redis_lock.rb`_

---

**You**

add idempotency to refunds

---

**LevelCode**

Here is the plan:

## Steps

```rb
Redis.set(key)

## It scrubs, and that isn't incidental

`chat-sessions-design.md` §10 already decided this:

> Transcripts contain code. […] Anything that later *shares* a session (LevelLinks) **must scrub — that is that feature's burden**, recorded here so it is not forgotten.

Export is the first surface that shares one, so `redactSecrets` — built last week for project memory — is passed in **at the call site** rather than baked into the renderer. The scrub is visible where it happens instead of being a property you have to know the module has, and there's a test pinning that the default is identity.

## Roles are bold labels, never headings

A turn routinely contains `## …` and fenced code. A heading-based role label gets visually outranked by the content it's supposed to delimit, and `### LevelCode` above a reply opening with `#` reads as though the model wrote the section title. Bold + a rule survives every renderer at every nesting depth — pinned by a test that feeds a turn starting with `# Plan`.

The success toast counts **turns, not characters**: "Copied 14 turns" tells you whether you got the session you meant.

## The fifth button

The action row is two fixed-height lines and `flex-wrap: nowrap` — buttons that don't fit don't wrap, they **overflow**. Five labelled buttons need ~300px; a sidebar is routinely narrower.

So the card is now a container and the labels collapse below 360px, leaving icons at ~184px. Every button already carried `title` + `aria-label`, so nothing is lost to a pointer or a screen reader — only to the eye, and only when there's no room.

That guard is **tested in both sheets**, because the failure is invisible in a wide window: whoever adds a sixth button won't see it break. A user with a narrow sidebar will.

> Both webviews carry the card, and the block between the `SESSIONS-PURE` markers must stay byte-identical. `sessionsView.html` is synced — the existing byte-identity test caught exactly what it was written for.

## Tests

5 new in `sessionEvents.test.js` (structure, heading safety, the scrub, the opt-in, degenerate sessions), 2 in `webviewCss.test.js`, and the action-row test updated to five buttons.

| bypass | result |
| --- | --- |
| drop the `@container` query | 13/15 |
| drop `container-type` | 13/15 |
| drop `aria-label` | 14/15 |
| drop the export scrub | 10/13 |

All **32** suites green.

## Where M4 stands after this

| | |
| --- | --- |
| Conflict reconciliation | ✅ |
| Poisoning red-team | ✅ (#64) |
| Export | ✅ this PR — memory-set export still open |
| Decayed-entry recall | ⬜ |

The M4 export slice (levelcode-sessions-experience.md §6): one action turns a
session into a clean Markdown transcript for pasting into a PR, and it is the
seed of a later share-a-run.

**It scrubs, and that is not incidental.** chat-sessions-design.md §10 already
decided this: transcripts at rest are the same trust class as your code, but
"anything that later *shares* a session must scrub — that is that feature's
burden." Export is the first surface that shares one, so redactSecrets — built
last week for project memory — is passed in at the call site rather than baked
into the renderer, so the scrub is visible where it happens instead of being a
property you have to know the module has.

**Roles are bold labels, never headings.** A turn routinely contains `## …` and
fenced code; a heading-based role label is visually outranked by the content it
is supposed to delimit, and `### LevelCode` above a reply opening with `#` reads
as though the model wrote the section title. Bold plus a rule survives every
renderer at every nesting depth.

The success message counts TURNS, not characters: "Copied 14 turns" tells you
whether you got the session you meant.

**The fifth button.** The action row is two fixed-height lines and
`flex-wrap: nowrap`, so buttons that do not fit do not wrap — they overflow.
Five labelled buttons need ~300px and a sidebar is routinely narrower, so the
card is now a container and the labels collapse below 360px, leaving icons at
~184px. Every button already carried title + aria-label, so nothing is lost to a
pointer or a screen reader — only to the eye, and only when there is no room.

Both webviews carry the card, and the pure block between the SESSIONS-PURE
markers must stay byte-identical across them — sessionsView.html is synced, which
is the existing test catching exactly what it was written for.

Tests: 5 new in sessionEvents.test.js (structure, heading safety, the scrub, the
opt-in, degenerate sessions), 2 in webviewCss.test.js pinning the overflow guard
in BOTH sheets, and the action-row test updated to five buttons. The CSS guard is
pinned because the failure is invisible in a wide window: whoever adds a sixth
button will not see it break — a user with a narrow sidebar will.

Verified non-vacuous: dropping the container query 13/15, the container-type
13/15, the aria-label 14/15, the export scrub 10/13. All 32 suites green.
Copilot AI lite review requested due to automatic review settings August 14, 2026 22:55

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Adds a “Copy as Markdown” export path for chat sessions in the LevelCode AI extension, producing a shareable, scrubbed transcript suitable for pasting into PRs and optionally saving to disk.

Changes:

  • Introduces sessionEvents.toMarkdown() to render a session transcript with provenance and turn delimiters.
  • Adds an export session-card action wired through extension.js to copy to clipboard and optionally save as a .md file.
  • Updates both sessions webviews (chat + sessions view) to include a fifth action button and container-query CSS to prevent overflow in narrow panes, with tests pinning the behavior.

Reviewed changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
extensions/levelcode-ai/sessionEvents.js Adds Markdown transcript renderer for session export.
extensions/levelcode-ai/extension.js Wires new export action: copy Markdown to clipboard + optional save dialog.
extensions/levelcode-ai/media/chat.html Adds export button + CSS container query to collapse labels in narrow panes.
extensions/levelcode-ai/media/sessionsView.html Mirrors export button + narrow-pane label collapse behavior for the sessions view.
extensions/levelcode-ai/test/sessionEvents.test.js Adds tests covering transcript structure, heading-safety, opt-in redaction, and degenerate sessions.
extensions/levelcode-ai/test/sessionsUi.test.js Updates sessions card action-row expectations to include the new export action.
extensions/levelcode-ai/test/webviewCss.test.js Adds tests pinning container-query behavior and accessible button labeling.
docs/levelcode-sessions-memory.md Updates M4 status/docs to mark session export complete and document scrub/role-label rationale.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread extensions/levelcode-ai/sessionEvents.js
Comment thread extensions/levelcode-ai/extension.js Outdated
…ename

Two review findings on #65:

- toMarkdown trimmed each turn's text, which changes Markdown semantics: a
  leading 4-space indent (indented code block) got de-indented and a trailing
  "  " (hard line break) was eaten. Turn text is now emitted verbatim aside from
  redaction — the `**role**` blank line already supplies the blank an indented
  block needs. Pinned by a new test (indented code + trailing hard-break survive).
- The default save-dialog filename was derived from the RAW entry.title, not the
  scrubbed copy toMarkdown makes — and the filesystem sanitiser only strips
  slashes/colons, so a credential's own characters survive it (ghp_ABC… →
  ghp-abc…). entry.title now goes through redactSecrets before the stem, so a
  token in a title can't leak into the save path. Pinned by a source invariant.

32 suites green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@ndemianc
ndemianc merged commit 2a4b50b into develop Aug 15, 2026
1 check passed
@ndemianc
ndemianc deleted the feat/session-export branch August 15, 2026 00:32
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.

2 participants