mirror of
https://github.com/stablyai/orca.git
synced 2026-09-28 08:02:43 +00:00
* fix(mobile): make native-chat file links and path citations tappable (STA-3331)
- Linkify POSIX absolute paths in chat prose (leading-/ regex alternative;
URL guard now keys off the char before the matched slash)
- Parse agent-style path:line(:col) citations in prose, code spans, and the
open flow; line/column ride into the mobile file preview route
- Route non-web markdown hrefs (file: URIs, relative/absolute paths) to the
file opener instead of silently dropping them; unknown schemes stay dead
- Resolve chat paths against the worktree root, not the terminal's live cwd
- Reuse the terminal tap-to-open flow for chat taps (haptic, preview route,
tab activation with retries) via a shared identity-stable hook, and toast
on misses instead of silent no-ops
- Keep snake_case paths whole (intraword underscores are literal text),
scan bold/italic/strike spans for paths, split trailing punctuation off
autolinks, and let taps land while the composer keyboard is up
* fix(mobile): harden chat file tap handling
* refactor(chat): share native chat href routing
* fix(mobile): detect files directly under path roots
* fix(mobile): keep inline tokens and dunder paths intact around emphasis
Review follow-ups on the chat file-link work:
- A rejected intraword `_` token left the scan index past its closing
underscore, so every inline token between two snake_case words was
swallowed and rendered as literal source — including markdown links,
which became untappable. Rescan from just past the opening delimiter.
- Treat a path separator as an intraword flank so `src/__init__.py` and
`a/__tests__/x.ts` stay whole; previously they rendered as bold plus a
remnant that the new absolute-root pattern turned into a tap on `/x.ts`.
- Bound the `:line(:col)` tail so `src/app.ts:1e3` and `:80%` no longer
parse a line number, while a cited range still opens its first line.
- Route chat tap failures through the composer banner (toast fallback):
chat taps happen with the keyboard up, which covers the toast.
- Drop the tap-handler mirror's dep list; the call site rebuilds its
accessors every render, so it could never skip on a route that
rerenders per keystroke.
* Revert "fix(mobile): keep inline tokens and dunder paths intact around emphasis"
This reverts commit 308bfaf22b.
* fix(mobile): preserve chat file-link parsing and feedback
74 lines
2.4 KiB
TypeScript
74 lines
2.4 KiB
TypeScript
import { describe, expect, it } from 'vitest'
|
|
import {
|
|
isIntrawordUnderscoreToken,
|
|
trimAutolinkTrailingPunctuation
|
|
} from './markdown-inline-token-rules'
|
|
|
|
describe('isIntrawordUnderscoreToken', () => {
|
|
it('rejects snake_case emphasis spans', () => {
|
|
const text = 'src/foo_bar.ts and src/baz_qux.ts'
|
|
const index = text.indexOf('_')
|
|
const token = text.slice(index, text.lastIndexOf('_') + 1)
|
|
expect(isIntrawordUnderscoreToken(text, index, token)).toBe(true)
|
|
})
|
|
|
|
it('keeps standalone emphasis', () => {
|
|
expect(isIntrawordUnderscoreToken('say _hello_ now', 4, '_hello_')).toBe(false)
|
|
expect(isIntrawordUnderscoreToken('_hello_.', 0, '_hello_')).toBe(false)
|
|
})
|
|
|
|
it('rejects emphasis closed against a following word', () => {
|
|
expect(isIntrawordUnderscoreToken('_foo_s bar', 0, '_foo_')).toBe(true)
|
|
})
|
|
|
|
it('rejects dunder emphasis inside a path', () => {
|
|
expect(isIntrawordUnderscoreToken('src/__init__.py', 4, '__init__')).toBe(true)
|
|
expect(isIntrawordUnderscoreToken(String.raw`src\__init__.py`, 4, '__init__')).toBe(true)
|
|
})
|
|
|
|
it('ignores non-underscore tokens', () => {
|
|
expect(isIntrawordUnderscoreToken('a*b*c', 1, '*b*')).toBe(false)
|
|
})
|
|
})
|
|
|
|
describe('trimAutolinkTrailingPunctuation', () => {
|
|
it('splits sentence punctuation off the URL', () => {
|
|
expect(trimAutolinkTrailingPunctuation('https://x.com/a.')).toEqual({
|
|
url: 'https://x.com/a',
|
|
trailing: '.'
|
|
})
|
|
expect(trimAutolinkTrailingPunctuation('https://x.com/a,')).toEqual({
|
|
url: 'https://x.com/a',
|
|
trailing: ','
|
|
})
|
|
expect(trimAutolinkTrailingPunctuation('https://x.com/a?!')).toEqual({
|
|
url: 'https://x.com/a',
|
|
trailing: '?!'
|
|
})
|
|
})
|
|
|
|
it('keeps balanced parens and strips unbalanced ones', () => {
|
|
expect(trimAutolinkTrailingPunctuation('https://x.com/a_(b)')).toEqual({
|
|
url: 'https://x.com/a_(b)',
|
|
trailing: ''
|
|
})
|
|
expect(trimAutolinkTrailingPunctuation('https://x.com/a).')).toEqual({
|
|
url: 'https://x.com/a',
|
|
trailing: ').'
|
|
})
|
|
})
|
|
|
|
it('handles long unmatched closing-parenthesis tails', () => {
|
|
const url = 'https://x.com/a_(b)'
|
|
const trailing = ')'.repeat(4096)
|
|
expect(trimAutolinkTrailingPunctuation(`${url}${trailing}`)).toEqual({ url, trailing })
|
|
})
|
|
|
|
it('leaves clean URLs untouched', () => {
|
|
expect(trimAutolinkTrailingPunctuation('https://x.com/a')).toEqual({
|
|
url: 'https://x.com/a',
|
|
trailing: ''
|
|
})
|
|
})
|
|
})
|