* feat: expand storage usage reporting with disk breakdown
* feat: add providers admin view
* feat(storage): add background reorganization and admin browser
Adds background-driven storage reorganization for moving model files
when the storage template changes, with an admin interface to preview
changes, check impact, monitor progress, and browse the storage tree
built from plate records rather than filesystem walks.
* feat: new auth admin settings page + OIDC support
* test: fix failing test
* fix(import): stop flagging models that mention robots as a MakerWorld CAPTCHA, and let a manual retry try again
isCaptchaChallenge looked for "captcha" or "robot" in every top-level string of a reply,
design titles and summaries included, so a model named "Robot ..." failed as a CAPTCHA on
every retry and started the 2h cooloff for every other import. Only a captcha key or the
reply's own error, message or msg text counts now.
The cooloff also failed a retry straight away after the user solved the puzzle in the
browser, which is what the error message tells them to do. Starting or retrying a queue job
by hand, a bulk start or retry, and POST /import/jobs/:id/retry now lift it. Automatic
collection syncs still respect it.
* feat(extension): offer "Add to the queue" when MakerWorld blocks an import
A CAPTCHA or the daily download limit won't clear by clicking Import again, so the error
panel lets an admin park the link in the queue with the same collection and profile scope.
* fix(import): let a queue job cut off by a restart be started or retried
A restart marks a running job ERROR with its links still PENDING or RUNNING. The queue page
only offered Start for PAUSED jobs and Retry for jobs with FAILED links, and the API agreed,
so such a job could never run again. The same held for a job whose runner crashed.
Start now takes an ERROR job with links waiting, and a retry reruns links an interrupted
run never reached even when none of them failed.
MakerWorld can leave a design's defaultInstanceId pointing at a print profile that no longer
exists. pickMakerworldInstanceId (extension) and makerworldInstanceIdFromNextData (backend)
returned it unchecked, so the download-link request went to the dead profile and MakerWorld
answered 403 "The client does not have access rights to the content." Use the default only when
it is still one of the design's instances, otherwise the first, as selectMakerworldProfiles
already does.
Fixes#14
Claude-Session: https://claude.ai/code/session_01KGCb65Zhzwd3daF862ZE6R
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
So the web app's Browse tab can list the user's collections there
without asking for a username. Thingiverse's comes from the page
header's own account link; Printables' from its API's `me`, asked with
the session token the way the site does. Sent only when it changes.
Every collection's Sync configuration now works before it's synced: a
Browse tab lists the user's own collections on MakerWorld (through
their cookie, private ones included), Printables and Thingiverse (by a
username asked once), and takes any collection's link. Each provider's
collections are drawn in its own colours and manner.
On Printables and Thingiverse the collection's current models are
imported by the sync's first check. MakerWorld rate-limits bulk
imports, so there only models added from now on come in. Settings can
be picked before turning sync on.
Add POST /collection/:id/items. Thingport Grab uses it to file a
MakerWorld collection's already-imported models instead of one request
per model, falling back on older instances.
A session token outlives a password change, so a stale saved password
only surfaced days later as failing imports. Check the connection by
signing in (at most every 10 minutes) at browser start, before showing
the in-page icon, and when the popup opens.
When it fails, the icon shows a "not connected" badge whose dialog says
whether the credentials were rejected or the instance didn't answer,
and opens the popup's edit form. The toolbar icon gets a badge too.
Concurrent calls with an expired token now share one sign-in.
The collection panel sent one /import/status request per model, all at
once. When any failed it counted the model as new, so a collection
already imported could offer to import almost everything again.
Add POST /import/status/batch and use it, falling back to paced
per-model checks on older instances. A failed check now shows an error
instead of guessing.
- A MakerWorld CAPTCHA was reported as "No downloadable model file
found". The extension's real-click download looked for a "Download"
button that no longer exists in any language: the downloads sit in the
menu behind the arrow next to "Open in Bambu Studio". It now opens that
menu and clicks "Download 3MF", else "Download STL/CAD" then "Download
All" (a zip), matched in all 12 MakerWorld UI languages. A 418/CAPTCHA
reply is detected by both the extension and the backend, which now
answers 429 with a clear message instead of trying more endpoints.
- A CAPTCHA now pauses the guided import on the same model instead of
ending it. A docked panel leaves MakerWorld's puzzle reachable, and
Resume retries that model. A "Wait between models" choice (5 s to
5 min) paces long unattended runs; the countdown runs on the next
model's page, since the extension background is killed after 30 s idle.
- The collection scan stopped early (e.g. 40 of 56) on localized pages:
the end marker was English-only, so a 4 s no-new-cards timeout ended
the scan on any slow page load. It now stops at the collection's own
model count from the page data, knows the end marker in every
language, and keeps the timeout only as a long fallback.
- The guided import hung every few models with nginx logging 499s:
Chromium kills an extension service worker whose fetch() takes over
30 s to answer. POST /import?async=1 now returns a task id to poll at
GET /import/tasks/:id; an older instance ignores the flag and answers
as before.
- The overlay's "Import next" button was always visible instead of only
after a step looks stuck.
* feat: link queue with retries and update-from-source
Pasting several model links now imports them as one background queue
job (LINKS), one at a time, with an in-place retry for transient
failures and a retry button for whatever still fails. Failed links are
kept per item, so a retry reruns only those.
An imported model can also be refreshed from its source ("Update from
source" in the model menu, "Update model in Thingport" in the
extension). Only empty fields are filled, so an edit always wins and
tags are only added; missing files come in (for MakerWorld, the print
profiles the model doesn't hold yet) and an empty image slot is
filled.
* fix: show the import queue and log its execution
Feedback from testing a mixed batch: the provider chips only showed one
of the pasted links' providers (now every provider in the batch lights
up), the queue had no view while it ran, and the pod's logs said nothing
about what was importing.
The progress bar is now clickable and opens the queue as a list: one row
per link with its status (waiting/importing/imported/failed) and why it
failed. Items carry a RUNNING state so the current link is visible, and
a finished batch with failures shows up in warning colors instead of the
quiet bar. The runner logs each link and the job summary with an
[import] prefix, in the console style the rest of the backend uses.
* feat: admin import queue page with start, pause and retry
Links can now wait in a PAUSED LINKS job until someone starts them. The
new Administration > Import queue page lists every user's batch imports
and starts, pauses, retries and deletes link-list jobs as their owner,
with the owner's saved MakerWorld cookie. Start all and Retry all failed
run jobs one at a time across users, so a bulk run never bursts the
providers from one IP.
POST /import/queue parks one link in the user's paused queue with its
own collection, profile scope and title, keeping a single "N links
waiting" notification up to date. Pausing is cooperative: the runner
finishes the link it is on, then stops, and a job cut off by a restart
can be paused and started again to finish.
* feat(extension): send to queue toggle
A "Send to queue" switch under "Extension enabled" makes the single
model panel add the link to the instance's paused import queue instead
of importing it, keeping the panel's collection and print profile
choice. Choosing files out of a zip still imports directly, since it
needs the download right away.
Sends the model page's design data (title, tags, description, creator, cover,
gallery, categories) with a MakerWorld import, so the backend doesn't have to
fetch the page again for them -- only when that data is this model's, and
small enough to send.
MakerWorld only writes its page data (__NEXT_DATA__) on a full page load, so
after clicking through to another model it still describes the first one, and
the import panel had no profile list to offer. The extension now fetches the
current model's page for its data when the embedded data is stale, and keeps it
for that model while the tab is open, which also restores per-profile download
resolution after in-app navigation.
The "Print profiles" options now depend on the model's profiles: "all designer
print profiles" only when the designer uploaded more than one, and "designer &
community" only when there are community profiles. The first option is renamed
"Currently selected print profile".
On a MakerWorld model page with more than one print profile, the import panel
offers "Print profiles": the one from the link (the default), all designer
print profiles, or designer and community print profiles. Each profile's file
is resolved in the page and imported in turn, added to the same model, with
progress shown and a summary at the end.
Extension versions are now derived from Conventional Commits that touch
extension/ (semantic-release-monorepo), instead of being bumped by hand.
The new "Extension store release" workflow runs every Friday at 13:00
Europe/Warsaw, or manually: it bumps package.json, writes CHANGELOG.md,
tags thingport-grab-v<version>, creates a non-latest GitHub release with
the store zips, and publishes to Edge Add-ons in a separately retryable
job.
The per-merge workflow keeps building the extension-latest zips and the
signed Firefox .xpi, but no longer publishes to Edge.