Skip to content

Add folder placement cards to the popup - #81

Merged
bmichelsen merged 25 commits into
mainfrom
40-add-folders
Aug 15, 2026
Merged

Add folder placement cards to the popup#81
bmichelsen merged 25 commits into
mainfrom
40-add-folders

Conversation

@bmichelsen

@bmichelsen bmichelsen commented Aug 14, 2026

Copy link
Copy Markdown
Member

Closes #40

What does this PR do?

Adds folder support to the popup as placement cards, per the design explorations (2b structure, refined over several rounds against dev):

  • The My Links card with the personal tags and a checkbox: checked by default, uncheckable for folder-only saves (my_links API flag). Unchecking greys the card; selections survive a re-check.
  • One card per folder with its own folder-scoped tag autocomplete (existing tags by usage or create-by-name). "+ Add to folder" opens a searchable Tom Select picker — the same control family as the tag fields — and reads "+ Add to another folder" once one exists.
  • Already-saved links open distinctly: a green check with "Saved N days ago" (from lookup's saved_at), pre-filled cards, and a "Save changes" button. The popup reveals only after lookup composes the layout, so nothing pops into place.
  • One explicit save sends the full desired state atomically: POST /urls (additive) for new links, PATCH /user_bookmarks/:id for existing — removing a card removes that placement (to folder trash), unchecking My Links trashes the personal save. Deselecting everything on a saved link turns the button into "Move to trash"; a new link with nothing selected keeps Save disabled.
  • Save feedback lives in the button ("Saving…" → "Saved ✓") so the popup never grows; the result area below is errors-only, with the API's error message parsed out.
  • Tag suggestions open downward, sized per open to the room below the field (whole rows, three-row floor).
  • Compact restyle per the explorations (420px, indigo-on-cream, round pills) with the bucket mark + wordmark as the header; existing button styles kept.

Version 0.4.2 → 0.5.0 (minor: new feature + UI change; store screenshots need refreshing per RELEASING.md). Branch history note: the UI was first built as exploration 2b, reworked to 2a for comparison, and returned to 2b — the net diff is the final flow.

Depends on linkbucket/linkbucket#2991 (folders/placements API incl. my_links and saved_at). Deploy the backend first; older extension builds are unaffected (every new key is optional).

How to test

  1. Backend deployed; load the extension from dist/.
  2. New link: tag My Links, add a folder card (try a shared one — meta shows "shared · N people"), tag it, Save. Verify on /links and in the folder with its folder tags.
  3. Reopen: green "Saved today" header, cards pre-filled, button "Save changes". Edit a folder tag and remove a card, Save — the placement lands in the folder's trash.
  4. Uncheck My Links, Save: the link leaves /links (to trash) while the folder keeps it. Reopen: checkbox comes back unchecked.
  5. On a folder-only link, remove the last card: the button flips to "Move to trash"; after it, the popup becomes a fresh save form.
  6. A collaborator on the folder page sees cards appear/disappear live.
  7. "Change API keys" resets everything; with >0 saveable state the button labels follow (Save / Save changes / Move to trash).

Checklist

  • npm test passes (55)
  • npm run lint passes
  • npm run format:check passes
  • npm run build succeeds for both targets (addons-linter: 0 errors)
  • Tested manually in Chrome and/or Firefox — pending the backend deploy (extension only talks to production); flows verified continuously against dev via a locally patched build plus a stubbed headless harness

🤖 Generated with Claude Code

A saved link is now presented as a stack of placements: the My Links
card always on top carrying the personal tags, plus one card per shared
folder, each with its own folder-scoped tag autocomplete (existing tags
or created by name). A dashed "Also add to a shared folder" affordance
appends cards from the accessible-folders list, and reopening an
already-saved page pre-fills the cards from the lookup's placements.

One explicit save sends everything atomically: POST /urls with the
placements array for a new link, PATCH /user_bookmarks/:id with the full
desired state for an existing one - so removing a card removes that
folder placement on save. Requires linkbucket.app to serve the
placements API (linkbucket/linkbucket#2991); deploy that first per
RELEASING.md.

Tom Select handling in tags.js becomes a factory so every card owns an
instance; the My Links singleton API is unchanged on top of it.
The heading was plain text, so nothing in the popup carried the actual
Linkbucket logotype. The wordmark from the web app replaces the h1 text
(kept as the image's alt), making the popup recognizably official at a
glance; the skateboarding-bucket illustration stays and becomes
decorative for screen readers.
Clicking the add-folder affordance with no addable folders (none exist,
all are placed, or the fetch failed) silently did nothing. The button
now reads "No folders to add" and stays disabled until a card removal
makes a folder addable again.
@bmichelsen bmichelsen linked an issue Aug 14, 2026 that may be closed by this pull request
The card stack implemented exploration 2b; the chosen design is 2a:
destination first, tags second. Step 1 lists every accessible folder as
a checkbox row under a locked, always-checked My Links (with a search
filter once the list grows); step 2 shows one tag row per checked
destination, each row's autocomplete scoped to its own folder. Checking
a folder adds its row, unchecking removes it - and on an already-saved
link, unchecking removes that placement on save, so the checklist is
also the removal UI. Reopening pre-checks the folders the link lives in
and notes "Already in your bucket".

Same save payload and API as before; folders now load on popup open
instead of on demand. My Links stays locked because the backend always
saves it - making it uncheckable (folder-only save) would be a small
API addition if wanted later.
@bmichelsen bmichelsen changed the title Add folder placement cards to the popup Add folders to the popup with a folder-first flow Aug 14, 2026
Reverts the folder-first rework after a hands-on comparison of both
directions: the cards keep each folder's tag field inside that folder's
card so tags can't land in the wrong scope, the popup costs nothing when
folders aren't used (no folder list rendered or fetched on open), a
reopened link shows exactly the placements that exist, and removing a
placement is a deliberate click on the card instead of an easy-to-miss
unchecked box.

This reverts commit dd425e0.
The lookup hit was deliberately silent: the only tell was pre-filled
tags, and the button copy only changed after saving. With placement
cards now pre-filled too, say it outright so the popup reads as "edit
what exists" rather than "save a duplicate".
The popup kept its original proportions - 470px wide, 52px inputs,
19px labels - which read oversized next to the exploration mocks.
Adopt their scale and palette instead: 400px wide, 38-40px fields, 13px
inputs, soft warm borders, deep-indigo ink on the cream background, and
fully round tag pills. The existing buttons stay (peach pill submit,
underlined key link), only trimmed to the new scale.

The header swaps the large skateboarding illustration for a compact
official lockup: the bucket mark from the app favicon beside the
wordmark.
@bmichelsen bmichelsen changed the title Add folders to the popup with a folder-first flow Add folder placement cards to the popup Aug 14, 2026
The winning mockup from the design round: My Links loses its heavy
solid pill for a plain bold name with a checkbox - checked by default,
and unchecking greys the card, hides its tag field (selections survive
a re-check), and saves to folders only via the new my_links API flag.
Save is blocked when nothing is selected at all.

The readonly URL field becomes a page box: the tab's title bold with
the URL beneath, the URL standing in when a page has no title - no new
permissions, the title comes with tabs.query. The add affordance stops
implying folders are shared ("+ Add to folder", then "+ Add to another
folder" once one card exists), and the save button is just "Save".
The compact pass overshot: 13-14px type in 38-40px fields with 10-14px
gaps read as cramped. Settle halfway between that and the original
proportions - 15px base, 14px inputs, 44px fields, 42px tag controls,
18px section rhythm, and a 420px popup - keeping the compact layout's
character without the squeeze.
Chromium draws the text caret at the input's line-box height, so the
type-sized line-height left a half-height caret beside the visually
taller tag pills. A pixel line-height matching the pills fixes the
caret without touching the typed text size.
The line under the actions claimed permanent popup height for a message
that only applied to reopened links; the already-saved signal will get
a better home. The result area now collapses when idle and appears only
for transient save feedback.
The upward list was a leftover from the single-field layout, where the
tag field sat at the popup's bottom edge with nothing beneath it. In
the card layout it buried the header and clipped at the popup's top.
Every field now has the add-folder and actions rows below it - 129px in
the tightest state (My Links only) and ~90px more per folder card - so
the list opens downward and, on each open, fills the space between the
field and the popup's bottom edge, quantized to whole rows with a
three-row floor matching that tightest state.
The native select was the one control left outside the popup's design
language: OS-drawn menu, chevron pinned to the edge, no search. The
picker now mounts the same Tom Select as the tag fields in single mode,
so every dropdown shares one look and the folder list is searchable.

Unlike the tag fields, the picker sits at the popup's bottom with no
room below, so while choosing the body extends to fit the list (the
popup window resizes to content) and snaps back on close. The dropdown
room-sizing helper moved to tags.js for both controls to share.
The picker's inner search input still inherited the generic form-input
chrome (44px height, padding), puffing the single-mode control up past
the tag fields - the neutralizing override was multi-only, and the
single control's min-height sat 2px above the tag controls' rendered
height. Both now land at 42px.

Picking the first folder flashed a taller window for a frame: the card
was added immediately while the picker teardown was deferred a tick, so
card + picker + reserved height stacked briefly, then shrank. Later
picks didn't flash only because the popup was already near Chrome's
height cap. Card add now happens in the same deferred tick as the
teardown, so the popup reflows once.
Whether a link was already saved was invisible: the status line at the
bottom was removed, leaving pre-filled tags as the only tell. Per the
explorations' already-saved treatment, a reopened link now opens with a
soft-green check circle and "Saved N days ago" (from the lookup's new
saved_at) between the brand and the page box, and the submit button
reads "Save changes" instead of "Save". New links show neither.
"Saving..." and the success message rendered in the result area below
the actions, so every save grew the popup window by a row. The whole
lifecycle now lives in the button - Saving..., Saved with a check, then
back to its idle label - in a fixed-size box so the popup height never
moves. The result area below is for errors only, where the extra row is
worth it.
The popup painted immediately in its new-link state and mutated as the
async work landed: the title, the saved header, and the folder cards
each popped into place, shifting everything below. The form now stays
hidden until the lookup has composed the final layout and fades in
once, complete. A 400ms cap keeps a slow network from holding the
popup blank - then late data pops in, the rare case instead of every
open.
Four review catches: a rejection in the popup's async init left the
form permanently hidden (the reveal now proceeds regardless); removing
a folder card mid-save re-enabled the button through the selection
guard, which now respects a busy flag; folder-only saves sent the
hidden My Links field's tags, creating user tags that attached to
nothing; and API errors displayed as raw JSON instead of their error
message. The save button label also had two sources of truth - one
refresh path owns it now.
Comments that restated the adjacent code are gone; the keepers carry a
constraint and got shorter. The setValues comment now says what it
actually guards: setValue silently drops ids with no registered option.
Also from review: addFolderCard was exported with no external caller,
the dropdown row height existed as a bare 38 in three places (now a
shared constant), and a small accessibility pass - the save button
announces its label changes, the My Links checkbox has an accessible
name, and the decorative check glyph is hidden from screen readers.
Reopening a folder-only link and removing its last card left a
disabled Save with no explanation - the user's intent (take this link
out of the folder) had nowhere to go. On a saved link, deselecting
every place now flips the button to "Move to trash": the existing
full-state PATCH trashes the personal save and every placement, and the
popup then becomes a fresh save form so a change of heart re-saves via
POST. A new link with nothing selected keeps the disabled button -
there is nothing to do. The selection callbacks now refresh the label
too, not just the disabled state.

Copilot AI 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.

Pull request overview

Adds folder placement cards and atomic placement editing to the popup, alongside refreshed saved-link feedback and styling.

Changes:

  • Adds folder-scoped cards, tags, and searchable folder selection.
  • Supports editing, trashing, and folder-only saves.
  • Updates tests, documentation, branding, and release versions.

Reviewed changes

Copilot reviewed 15 out of 18 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
src/popup/popup.js Coordinates placement and save flows.
src/popup/placements.js Implements folder placement cards.
src/popup/tags.js Generalizes tag controls.
src/popup/utils.js Adds folder, payload, date, and error helpers.
src/popup/api.js Adds folder retrieval.
src/popup/popup.html Introduces the card-based interface.
src/popup/popup.css Restyles the popup and new controls.
tests/utils.test.js Tests new utility helpers.
tests/api.test.js Tests folder retrieval.
README.md Documents folder support.
CONTRIBUTING.md Updates the source layout.
CHANGELOG.md Records the 0.5.0 features.
package.json Bumps the package version.
manifest.chrome.json Bumps the Chrome version.
manifest.firefox.json Bumps the Firefox version.
assets/images/skate-bucky.svg Removes the previous illustration.
assets/images/lb-logo.svg Adds the new wordmark.
assets/images/bucket-mark.svg Adds the new bucket mark.

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

Comment thread src/popup/popup.js
Comment thread src/popup/popup.js
Comment thread src/popup/placements.js
Comment thread src/popup/utils.js Outdated
Comment thread package.json
Two Copilot catches on the POST/PATCH boundary. A successful first save
left the popup in create mode, so follow-up edits re-POSTed - and POST
is additive, meaning removing a just-added card silently didn't remove
it. After a POST the popup now re-runs the lookup and switches to
update mode (deferred saves stay additive; no bookmark exists yet). And
the reveal cap could show the form while the lookup was still pending,
letting a fast save POST an already-saved link - Save now stays gated
until init settles.
fetchFolders answered [] for failures and real empty lists alike, so a
transient error read "No folders to add" and dead-ended the button
until the popup reopened. Failures now resolve to null and the button
offers "Couldn't load folders. Try again", still clickable; only a
genuinely empty list keeps the dead-end label.
Elapsed 24h periods called a 23:55 save "today" at 00:05. The day count
now compares local calendar dates, so today and yesterday flip at the
user's midnight and stay put across DST.
@bmichelsen
bmichelsen merged commit 3b78c6b into main Aug 15, 2026
3 checks passed
@bmichelsen
bmichelsen deleted the 40-add-folders branch August 15, 2026 16:18
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.

Add folders

2 participants