Skip to content

feat: bulk-add messages from a pasted dialogue script#21

Draft
TibetOS wants to merge 2 commits into
mainfrom
claude/bulk-entry
Draft

feat: bulk-add messages from a pasted dialogue script#21
TibetOS wants to merge 2 commits into
mainfrom
claude/bulk-entry

Conversation

@TibetOS

@TibetOS TibetOS commented Jul 3, 2026

Copy link
Copy Markdown
Owner

Summary

Implements Roadmap Phase 2, item #14 — bulk/script entry, the roadmap's "huge time-saver for creators": paste a whole dialogue and build the thread in one click instead of composing message by message.

Based directly on main — first PR of the post-merge cycle, with CI now active.

What changed

  • src/script-parser.tsparseScript(): one Name: message per line; Me/You/I (any case) become sent messages, any other speaker becomes the contact; speakerless lines continue the previous message on a new line; timestamps advance one minute per message from a start time (wrapping past midnight); malformed start times fall back to the default.
  • Bulk Add card in the control panel — script textarea with format hint, "Add to chat" (append), and a "Replace existing" checkbox guarded by a confirm prompt. The textarea clears on success.
  • Composes with everything shipped previously: bulk-added messages autosave, export, and can then be edited individually (reactions, replies, images…).

Verification

  • pnpm test — 48/48 pass across 2 files (7 new parser unit tests + 3 new UI tests: append, replace, empty-script no-op)
  • pnpm lint — clean
  • pnpm build — succeeds
  • CI runs on this PR (first cycle with the ci: add CI workflow and Dependabot config #3 workflow active).

Notes

  • Multiple distinct "them" speakers all render as the single contact for now — correct for 1:1 chats; the parser's speaker names become per-participant senders when Phase 3 group chats land.
  • Next queued: JSON/.txt import-export, then shareable URL state.

🤖 Generated with Claude Code


Generated by Claude Code

Roadmap Phase 2, item #14.

- New parseScript utility: one 'Name: message' per line; Me/You/I (any
  case) map to sent messages, other speakers to the contact; speakerless
  lines continue the previous message; timestamps advance one minute per
  message from a start time, wrapping past midnight.
- Bulk Add card in the control panel: script textarea, 'Add to chat'
  button, and a 'Replace existing' checkbox guarded by a confirm.
- Unit tests for the parser (7) and UI tests for append, replace, and
  empty-script no-op.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SN53MtH3hvbHr7syhwu54q

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Code Review

This pull request introduces a 'Bulk Add' feature that allows users to paste a dialogue script and parse it into chat messages. It includes a new script parser utility, corresponding unit tests, and UI integration in the control panel with support for appending or replacing existing messages. The review feedback highlights a bug where lines with a speaker prefix but no content are incorrectly parsed, suggests adding a unit test to prevent regressions for this case, and recommends passing the user's current timestamp to the parser instead of relying on a hardcoded default start time.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread src/script-parser.ts
Comment on lines +44 to +54
const match = LINE_RE.exec(line)
if (match && match[2].trim()) {
const speaker = match[1].trim()
const sender = ME_ALIASES.has(speaker.toLowerCase()) ? "me" : "them"
messages.push({
text: match[2].trim(),
sender,
timestamp: formatTime(minutes),
status: "read",
})
minutes += 1

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

high

There is a bug when parsing lines that contain a speaker prefix but no message content (e.g., "Me: " or "Sarah: ").

Because match[2].trim() is empty, the condition match && match[2].trim() evaluates to false. The parser then falls into the else if or else blocks:

  1. If there are existing messages, it appends the trimmed line (e.g., "Me:") to the previous message as a continuation.
  2. If there are no existing messages, it treats the line as a speakerless leading line and adds a message with the text "Me:" sent by the contact ("them").

We should instead ignore lines that have a speaker prefix but no message content.

Suggested change
const match = LINE_RE.exec(line)
if (match && match[2].trim()) {
const speaker = match[1].trim()
const sender = ME_ALIASES.has(speaker.toLowerCase()) ? "me" : "them"
messages.push({
text: match[2].trim(),
sender,
timestamp: formatTime(minutes),
status: "read",
})
minutes += 1
const match = LINE_RE.exec(line)
if (match) {
const text = match[2].trim()
if (!text) continue
const speaker = match[1].trim()
const sender = ME_ALIASES.has(speaker.toLowerCase()) ? "me" : "them"
messages.push({
text,
sender,
timestamp: formatTime(minutes),
status: "read",
})
minutes += 1

Comment thread src/script-parser.test.ts
Comment on lines +41 to +45

it("returns an empty list for empty input", () => {
expect(parseScript("")).toEqual([])
expect(parseScript(" \n ")).toEqual([])
})

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

To prevent regressions, we should add a unit test that verifies lines with a speaker prefix but no message content are correctly ignored.

  it("ignores lines with a speaker prefix but no message content", () => {
    const msgs = parseScript("Me: hello\nSarah: \nMe: world")
    expect(msgs).toHaveLength(2)
    expect(msgs[0].text).toBe("hello")
    expect(msgs[1].text).toBe("world")
  })

  it("returns an empty list for empty input", () => {
    expect(parseScript("")).toEqual([])
    expect(parseScript("   \n  ")).toEqual([])
  })

Comment thread src/components/ControlPanel.tsx Outdated
Comment on lines +57 to +58
const handleBulkAdd = () => {
const parsed = parseScript(script)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

Currently, parseScript is called without the second argument, meaning bulk-added messages will always start at the hardcoded default of "10:30".

We can make this dynamic and respect the user's preferred time by passing the existing timestamp state (from the message form) as the startTime parameter.

Suggested change
const handleBulkAdd = () => {
const parsed = parseScript(script)
const handleBulkAdd = () => {
const parsed = parseScript(script, timestamp)

Address review feedback on #21:
- A speaker prefix with no content ('Sarah: ') was glued onto the
  previous message (or became a bogus leading message); such lines are
  now skipped, with a regression test.
- Bulk-added messages now start their timestamps from the compose
  form's Time field instead of the hardcoded 10:30 default.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SN53MtH3hvbHr7syhwu54q
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