Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
112 commits
Select commit Hold shift + click to select a range
f673099
Minor cleanup.
nedtwigg Sep 10, 2025
b92a4a5
Need to turn off biome `noUnknownAtRules` for tailwind v4 https://git…
nedtwigg Sep 10, 2025
423a7bc
Working straight from claude, refinement needed.
nedtwigg Sep 10, 2025
9377e7a
`CommentEnhancer` now just has `tableRow(spot: Spot): ReactNode` inst…
nedtwigg Sep 10, 2025
113586c
Take full advantage of tailwind v4.
nedtwigg Sep 10, 2025
4ea95c0
Update README.
nedtwigg Sep 10, 2025
49bc9b3
Propery initialize shadcn/ui
nedtwigg Sep 10, 2025
1274c11
biome:fix
nedtwigg Sep 10, 2025
2a72e6f
Add a playground for the table with hotreload.
nedtwigg Sep 10, 2025
7ba5e88
Improve commonality between prod and test.
nedtwigg Sep 10, 2025
da006a3
Renames for easier command-palette navigation.
nedtwigg Sep 10, 2025
4ed332c
We ned postcss at devtime.
nedtwigg Sep 11, 2025
44fdbb5
playground is alive
nedtwigg Sep 11, 2025
9e97447
Get the playground to match the popup exactly.
nedtwigg Sep 11, 2025
db9d46f
Improve docs.
nedtwigg Sep 11, 2025
adadfd8
Change width to 311px.
nedtwigg Sep 11, 2025
2ca0e68
Set the width in only one place.
nedtwigg Sep 11, 2025
0e17d9d
Make it wider.
nedtwigg Sep 11, 2025
c04643c
We were missing vite, which we were using through an undeclared trans…
nedtwigg Sep 11, 2025
10805c1
Unnecessary docs.
nedtwigg Sep 11, 2025
0381d9b
Add the octicons.
nedtwigg Sep 11, 2025
fbcfe43
Add some missing .gitignores
nedtwigg Sep 11, 2025
597cf79
Simplify.
nedtwigg Sep 11, 2025
72ce05d
Add a prototype straight outta claude.
nedtwigg Sep 11, 2025
398548d
Fake fix by manually creating the tailwind classes.
nedtwigg Sep 11, 2025
9c94e23
Fix tailwind v4
nedtwigg Sep 11, 2025
e3b92ca
biome fixup
nedtwigg Sep 11, 2025
3e67771
Improved layout.
nedtwigg Sep 11, 2025
bc3f583
Playground structure improvements.
nedtwigg Sep 11, 2025
592341d
fixup.
nedtwigg Sep 11, 2025
40827ef
Extremely minor cleanup.
nedtwigg Sep 11, 2025
193e743
Remove platform filter.
nedtwigg Sep 11, 2025
98f1645
Remove the type filter.
nedtwigg Sep 11, 2025
41ede88
Move the up/down to the column header.
nedtwigg Sep 11, 2025
f5d98ed
Tightened up a bit.
nedtwigg Sep 11, 2025
3a8e4d4
Move search into the table header.
nedtwigg Sep 11, 2025
cf6090a
More compression.
nedtwigg Sep 11, 2025
2e6f3b2
Progress.
nedtwigg Sep 11, 2025
9a70289
Remove all the author stuff.
nedtwigg Sep 11, 2025
0859ffd
Remove the public/private filter
nedtwigg Sep 11, 2025
5cd2826
Remove the private/public pills.
nedtwigg Sep 11, 2025
88493d9
Replace mention count with image count.
nedtwigg Sep 11, 2025
015cc84
Remove dangling icons
nedtwigg Sep 11, 2025
9109951
Lots of improvements.
nedtwigg Sep 11, 2025
0f3c0cc
Fix the reddit example
nedtwigg Sep 11, 2025
703e615
Getting pretty darn good.
nedtwigg Sep 11, 2025
b830d50
More compact.
nedtwigg Sep 11, 2025
f74ec80
Fixup
nedtwigg Sep 11, 2025
e7b70f1
Bulk actions bar now floats.
nedtwigg Sep 11, 2025
f296ac1
Add a mechanism for grabbing the stats of a draft.
nedtwigg Sep 11, 2025
620ed50
Tweaks.
nedtwigg Sep 11, 2025
96bfc80
More typing.
nedtwigg Sep 11, 2025
f1822fa
Refactor the row out of the table.
nedtwigg Sep 11, 2025
98d7c3c
Minor improvement.
nedtwigg Sep 11, 2025
66f73b0
Add a last-edit crumb.
nedtwigg Sep 11, 2025
b9e79d3
Cleaner.
nedtwigg Sep 11, 2025
856f3c5
starting point
nedtwigg Sep 12, 2025
874acc1
Use CVA to make our little badge things more maintainable.
nedtwigg Sep 12, 2025
4981ed1
Massage the types to better fit where we're headed.
nedtwigg Sep 12, 2025
613acc0
Add sent/unsent icons.
nedtwigg Sep 12, 2025
928618b
Show archive status.
nedtwigg Sep 12, 2025
f120ca9
Also show the open / not open status.
nedtwigg Sep 12, 2025
d889bc1
Prepare to make a multiselect control.
nedtwigg Sep 12, 2025
92fb56d
Refactor our multiple booleans into a combined `FilterState`.
nedtwigg Sep 12, 2025
7a28666
Homerolled multi-select.
nedtwigg Sep 12, 2025
6b32318
use twMerge so that hover and selection works.
nedtwigg Sep 12, 2025
2ee2b3d
another take
nedtwigg Sep 12, 2025
67835d4
A take.
nedtwigg Sep 12, 2025
faed7d5
Okay layout.
nedtwigg Sep 12, 2025
dc9845d
Remove filtering by image/code/link.
nedtwigg Sep 12, 2025
0920513
Inline the filter options.
nedtwigg Sep 12, 2025
ec3135b
Remove the archive controls.
nedtwigg Sep 12, 2025
3aaa1da
One step.
nedtwigg Sep 12, 2025
6c78fa4
Update.
nedtwigg Sep 12, 2025
e35167e
Make the border solidity work.
nedtwigg Sep 12, 2025
c16b60c
Fix border roundedness.
nedtwigg Sep 12, 2025
da59253
Fix the height of the multisegment thingy.
nedtwigg Sep 12, 2025
baaa698
Cleanup.
nedtwigg Sep 12, 2025
7238fcb
Better type checking.
nedtwigg Sep 12, 2025
fd1c267
Prepare for trash model.
nedtwigg Sep 12, 2025
377b2e0
Improve the search box.
nedtwigg Sep 12, 2025
7a2f780
Cleanup empty states.
nedtwigg Sep 12, 2025
3f9111c
Trash that's good enough for the foreseeable future.
nedtwigg Sep 12, 2025
666c3ab
Add a settings button.
nedtwigg Sep 12, 2025
d1132b1
Move our claude prototype to the real actual CommentSpots we're worki…
nedtwigg Sep 12, 2025
a647512
Pull `generateMockDrafts` out of `claude.tsx` into `replicaData.tsx`
nedtwigg Sep 12, 2025
eb527aa
Massaga data model towards our prototype.
nedtwigg Sep 12, 2025
e8c0b81
Merge branch 'main' into feat/table-styling
nedtwigg Sep 12, 2025
18fd786
Merge branch 'main' into feat/table-styling
nedtwigg Sep 15, 2025
dcfa6fa
Adapt the new github types to our domain.
nedtwigg Sep 16, 2025
a39d3f9
Move interfaces around.
nedtwigg Sep 16, 2025
79a7d51
It works.
nedtwigg Sep 16, 2025
2393a37
fixup snapshots
nedtwigg Sep 16, 2025
0f0aa4f
Refactor more playground stuff into the real enhancers.
nedtwigg Sep 16, 2025
0fbd95d
Add an enhancer to function for unknown types.
nedtwigg Sep 16, 2025
438b373
Navigate design out into its own thing.
nedtwigg Sep 16, 2025
9f4bb04
Factor `Badge` out into its own thing.
nedtwigg Sep 16, 2025
1d3d3df
Rename `statBadge` to `badgeCVA`.
nedtwigg Sep 16, 2025
1fdbfc5
Refactor `MultiSegment` out into its own thing.
nedtwigg Sep 16, 2025
937a564
Refactor some helper UI functions into `misc.ts`.
nedtwigg Sep 16, 2025
63e9149
Refactor from `CommentState` to `CommentTableRow`
nedtwigg Sep 16, 2025
5d37a27
Fully complete the wiring.
nedtwigg Sep 16, 2025
03eb964
This worked.
nedtwigg Sep 16, 2025
45201b4
Give `playground-styles.css` a distinct name to avoid confusion.
nedtwigg Sep 16, 2025
461bec3
Compress unnecessary css splitting.
nedtwigg Sep 16, 2025
43c4920
Put the playground back into the weird root, but fix tailwind scanning.
nedtwigg Sep 16, 2025
9323e1a
Merge branch 'main' into feat/table-styling
nedtwigg Sep 16, 2025
701e16f
Modify tailwind imports in a way that biome can handle.
nedtwigg Sep 16, 2025
eb04c0e
Unneeded.
nedtwigg Sep 16, 2025
b732985
Swap `class-variance-authority` and `clsx` for `tailwind-variants`.
nedtwigg Sep 16, 2025
965e8a4
Enable tailwind class sorting.
nedtwigg Sep 16, 2025
da33cfa
Update snapshots with sorted classes.
nedtwigg Sep 16, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ dist/
Thumbs.db

# playright
.playwright-mcp/
browser-extension/dist-playground/
browser-extension/playwright-report/
browser-extension/playwright/
browser-extension/test-results/
browser-extension/test-results/
22 changes: 10 additions & 12 deletions browser-extension/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,15 +33,15 @@ This is a [WXT](https://wxt.dev/)-based browser extension that

### Entry points

- `src/entrypoints/content.ts` - injected into every webpage
- `src/entrypoints/background.ts` - service worker that manages state and handles messages
- `src/entrypoints/popup` - html/css/ts which opens when the extension's button gets clicked
- [`src/entrypoints/content.ts`](src/entrypoints/content.ts) - injected into every webpage
- [`src/entrypoints/background.ts`](src/entrypoints/background.ts) - service worker that manages state and handles messages
- [`src/entrypoints/popup/popup.tsx`](src/entrypoints/popup/popup.tsx) - popup (html/css/tsx) with shadcn/ui table components

```mermaid
graph TD
Content[Content Script<br/>content.ts]
Background[Background Script<br/>background.ts]
Popup[Popup Script<br/>popup/main.ts]
Popup[Popup Script<br/>popup/popup.tsx]

Content -->|ENHANCED/DESTROYED<br/>CommentEvent| Background
Popup -->|GET_OPEN_SPOTS<br/>SWITCH_TO_TAB| Background
Expand All @@ -60,22 +60,20 @@ graph TD
class TextArea,UI ui
```

### Architecture
Every time a `textarea` shows up on a page, on initial load or later on, it gets passed to a list of `CommentEnhancer`s. Each one gets a turn to say "I can enhance this box!". They show that they can enhance it by returning something non-null in the method `tryToEnhance(textarea: HTMLTextAreaElement): Spot | null`. Later on, that same `Spot` data will be used by the `tableRow(spot: Spot): ReactNode` method to create React components for rich formatting in the popup table.

Every time a `textarea` shows up on a page, on initial load or later on, it gets passed to a list of `CommentEnhancer`s. Each one gets a turn to say "I can enhance this box!". They show that they can enhance it by returning a [`CommentSpot`, `Overtype`].
Those `Spot` values get bundled up with the `HTMLTextAreaElement` itself into an `EnhancedTextarea`, which gets added to the `TextareaRegistry`. At some interval, draft edits get saved by the browser extension.

Those values get bundled up with the `HTMLTextAreaElement` itself into an `EnhancedTextarea`, which gets added to the `TextareaRegistry`. At some interval, draft edits will get saved by the browser extension (TODO).

When the `textarea` gets removed from the page, the `TextareaRegistry` is notified so that the `CommentSpot` can be marked as abandoned or submitted as appropriate (TODO).
When the `textarea` gets removed from the page, the `TextareaRegistry` is notified so that the `CommentSpot` can be marked as abandoned or submitted as appropriate.

## Testing

In `tests/har` there are various `.har` files. These are complete recordings of a single page load.

- `pnpm run har:view` and you can see the recordings, with or without our browser extension.
- `npm run playground` gives you a test environment where you can tinker with the popup with various test data, supports hot reload
- `npm run har:view` gives you recordings of various web pages which you can see with and without enhancement by the browser extension

### Recording new HAR files

- the har recordings live in `tests/har`, they are complete recordings of the network requests of a single page load
- you can add or change URLs in `tests/har-index.ts`
- `npx playwright codegen https://github.com/login --save-storage=playwright/.auth/gh.json` will store new auth tokens
- login manually, then close the browser
Expand Down
5 changes: 5 additions & 0 deletions browser-extension/biome.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,9 @@
"noUnusedVariables": "error",
"useValidTypeof": "error"
},
"nursery": {
"useSortedClasses": "error"
},
"recommended": true,
"style": {
"noDefaultExport": "off",
Expand All @@ -60,12 +63,14 @@
"useTemplate": "error"
},
"suspicious": {
"noAssignInExpressions": "off",
"noConsole": {
"options": {
"allow": ["assert", "error", "info", "warn"]
}
},
"noExplicitAny": "off",
"noUnknownAtRules": "off",
"noVar": "error"
}
}
Expand Down
15 changes: 15 additions & 0 deletions browser-extension/package.json
Original file line number Diff line number Diff line change
@@ -1,25 +1,38 @@
{
"author": "DiffPlug",
"dependencies": {
"@primer/octicons-react": "^19.18.0",
"@types/react": "^19.1.12",
"@types/react-dom": "^19.1.9",
"@wxt-dev/webextension-polyfill": "^1.0.0",
"highlight.js": "^11.11.1",
"lucide-react": "^0.543.0",
"overtype": "workspace:*",
"react": "^19.1.1",
"react-dom": "^19.1.1",
"tailwind-merge": "^3.3.1",
"tailwind-variants": "^3.1.1",
"webextension-polyfill": "^0.12.0"
},
"description": "Syntax highlighting and autosave for comments on GitHub (and other other markdown-friendly websites).",
"devDependencies": {
"@biomejs/biome": "^2.1.2",
"@playwright/test": "^1.46.0",
"@tailwindcss/vite": "^4.1.13",
"@testing-library/jest-dom": "^6.6.4",
"@types/express": "^4.17.21",
"@types/har-format": "^1.2.16",
"@types/node": "^22.16.5",
"@vitejs/plugin-react": "^5.0.2",
"@vitest/coverage-v8": "^3.2.4",
"@vitest/ui": "^3.2.4",
"express": "^4.19.2",
"linkedom": "^0.18.12",
"postcss": "^8.5.6",
"tailwindcss": "^4.1.13",
"tsx": "^4.19.1",
"typescript": "^5.8.3",
"vite": "^7.1.5",
"vitest": "^3.2.4",
"wxt": "^0.20.7"
},
Expand Down Expand Up @@ -47,6 +60,8 @@
"dev:firefox": "wxt -b firefox",
"postinstall": "wxt prepare",
"test": "vitest run",
"playground": "vite --config vite.playground.config.ts",
"playground:build": "vite build --config vite.playground.config.ts",
"har:record": "tsx tests/har-record.ts",
"har:view": "tsx tests/har-view.ts"
},
Expand Down
26 changes: 26 additions & 0 deletions browser-extension/src/components/Badge.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { twMerge } from 'tailwind-merge'
import type { VariantProps } from 'tailwind-variants'
import { badgeCVA, typeIcons } from '@/components/design'

export type BadgeProps = VariantProps<typeof badgeCVA> & {
type: keyof typeof typeIcons
text?: number | string
}

const Badge = ({ text, type }: BadgeProps) => {
const Icon = typeIcons[type]
return (
<span
className={twMerge(
badgeCVA({
type,
}),
)}
>
{type === 'blank' || <Icon className='h-3 w-3' />}
{text || type}
</span>
)
}

export default Badge
51 changes: 51 additions & 0 deletions browser-extension/src/components/MultiSegment.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { badgeCVA, typeIcons } from '@/components/design'

interface Segment<T> {
text?: string
type: keyof typeof typeIcons
value: T
}
interface MultiSegmentProps<T> {
segments: Segment<T>[]
value: T
onValueChange: (value: T) => void
}

const MultiSegment = <T,>({ segments, value, onValueChange }: MultiSegmentProps<T>) => {
return (
<div className='inline-flex items-center gap-0'>
{segments.map((segment, index) => {
const Icon = typeIcons[segment.type]
const isFirst = index === 0
const isLast = index === segments.length - 1

const roundedClasses =
isFirst && isLast
? ''
: isFirst
? '!rounded-r-none'
: isLast
? '!rounded-l-none'
: '!rounded-none'

return (
<button
key={String(segment.value)}
className={`${badgeCVA({
clickable: true,
selected: value === segment.value,
type: segment.type,
})} ${roundedClasses}`}
onClick={() => onValueChange(segment.value)}
type='button'
>
{segment.type === 'blank' || <Icon className='h-3 w-3' />}
{segment.text}
</button>
)
})}
</div>
)
}

export default MultiSegment
Loading