fabrica
- 20 Devlogs
- 64 Total hours
✨ blazing fast ✨ parts library/search engine for robotics
✨ blazing fast ✨ parts library/search engine for robotics
hello again! my main focus for this devlog has been on experimenting with loading/transition behavior for queries. before, the page would remove all of the card components and just show a “Results: loading…” screen and an unstyled <li> loading element, but i wanted to improve that and make it more animated and less “flashy”.
the first part of the transition comes on the “leading edge”, when the user updates their query and information has not yet been fetched. if i let the previous cards and results stay up, the site will be perceived as laggy and not responsive. so, my instinct would be to place a “skeleton UI” but that in itself would be a DOM update/layout reflow, which i wanted to avoid for performance reasons. i tried blurring individual card elements, but i didn’t like how it looked. maybe i settle with a skeleton UI or come up with some other combination of CSS effects, i don’t know. either way, i didn’t want the effect to show up if the request hits the cache, so instead of using solid.js’s existing useTransition hook, i manually combined the underlying startTransition function with my own pending signal, which i only set to true if the query was not found in the cache. this does mean that on cache hits the code looks up the map two times (once on the initial check for the pending signal, another on the resource fetch), but i don’t think trying to optimize that away with a boolean or something would end up being faster than just letting v8 optimize the double Map lookup.
for the second part–or, trailing edge–of the interaction, i had this idea of constructing a view transition so that cards that showed up previously would move nicely to their resulting spot. that unfortunately made things really laggy, especially since view-transition-name would be declared on both the cards themselves and many items within the card, as those would need to be labeled for the view transition to the product page. the visual effect was really nice though, so my plan to make it work is to finally introduce virtual scroll… yes i know i’m fixing more javascript with more javascript, but at least the site works without it! right?!
additional minor thoughts:
hello! so far i’ve been working on responsive/adaptive design for the search page. i’m kinda putting off doing the individual pages in place of refining the search page.
fit-content that just makes everything break horribly, but i had to choose manual widths for making the logo adaptive instead of relying on the image width. for some reason the logo tile gradually increases width despite me setting the width as static (gah css!). at some point in the future i should integrate the UI work with browserstack/tests.<a> element instead of an <input> with focus intercepted. to keep compatibility with no javascript, it SSR’s as an <input> element before switching out to an <a> on component mount. this means that on mobile the keyboard won’t open and close when trying to search. unfortunately iOS safari doesn’t support autofocus…gap being janky by adding more padding, but now that i eliminated the element i could remove the padding composition and make everything the same width. this does mean that when scrolling down the curvature of the search bar/logo isn’t consistent with the title cards, but hey everything’s a compromise.yeah so i ended up working more…
<article> tag within the <li> which meant that all the flexbox stuff wasn’t applying properly. 😅overflow: hidden + whitespace: nowrap to work without completely breaking the flow of the page, but line-clamp did work!/search page, changed the tracked event for the initial search page preload and filtered so it was only mouse hovers, stricter pre-commit hook, font preload, and color changes.productive day of UI work today!
view-transition-name. i also made it adaptable on the search screen so it collapses to its smaller form by just cropping the svg instead of making another network request. though if i wanted that as flexible as possible i’d probably modify the <picture> element to also media-query-match against device width. that way graceful degradation also works JS-less.there are still a couple of things on my plate UI-wise:
w-full but idk i’m probably going to switch out the UI to more of a grid formation anyways.overlay: hidden, i’m not sure but am currently leaning towards some sort of blur/fade effect.<Suspense> boundary narrower. my current desired functionality is for all the cards to “gray out” but with the original item list length, before the resource is fetched and the dom update happens.okay, take two on writing through this devlog since stardance just consumed my writing since it went over the word limit…
i did a lot of work since my last devlog with the UI, from view transitions to SSR to SEO to wrestling with build tools and broken npm packges. i don’t have the energy to write through everything again but i do have this demo that shows off just how cool view transitions are, the speed of SSR (though i had to make it cold start, which again i can’t be bothered to write about why a second time), and graceful degradation for javascript-less environments. next up i’m working on some housekeeping stuff with sorting tailwind classes, then formatting the search page, wiring up view transitions between the search page and product pages, more DOM optimization with better solid.js state management, and maybe adding tanstack virtualizer (though it’s kinda overkill, but allows me to just set every image to eagerly load) and keeping support for no-JS.
please stardance team could you fix entries getting sent into the void i wrote a really good devlog entry the first time around…
i don’t have much to share so far, been reading documentation & doing miscellaneous work, but right now i’m getting myself carried away with color palettes and fonts and branding design first.
i designed a lil’ logo! i’ll update the banner for this project to include the logo and typography and whatnot, but i decided on fonts (dm sans for display, ibm plex sans/mono, i know hurr hurr very original). originally i was going to use space grotesk as the display/heading font but i didn’t want my website to look too much like axon robotics et al. and i didn’t want to use orbitron because i feel like the wordmark should be a distinct font/character.
i emailed gobilda asking about CAD file scraping and their GraphQL endpoint that I discovered (which unfortunately needs credentials…), which gives me an excuse to not worry about the parsing issues with the scraper until they email me back or a week has passed. so in the meantime, i’ll work on web design.
in terms of pure functionality though i did also add a static product page generator for each of the products, which was why i thought astro was a really great fit for this project since i’m focusing on moving more stuff to build-time and improving DX instead. i do want to set up an account system and auth and all that jazz because the next big feature would be automatic PRs from suggestions, that just adds/modifies a json file in a partdb overrides folder in the repo. that’ll also give me an excuse to validate my monorepo design by putting some of that logic in another package.
finally, i know in my ai policy i’m using ai very minimally and i’ve disabled ai features in vscode… but could we let this one slide :) why those hex codes specifically iykyk
IT WORKS!!!!!!!
okay so for some reason the type signature of the search library that i’m using, flexsearch, didn’t give the right type signatures for when i put in enrich: true on searching. i had to do some manual array processing, which probably slows down the results a bit but it’s still under 15 milliseconds max. it’s incredibly fast, it seems like the bundle would work for edge functions so it’d be similarly fast in real life, and i can’t wait to implement the service worker backend to make things even faster. from here i want to refine the querying so you can filter by manufacturer, categories, etc. and refine the database items. and also have a pretty UI :)
most of the parsing issues have been fixed! the final list is 1321 items, which is the same amount as previous scrapes, so i don’t think there are any issues there.
a couple things still worry me a bit (but they’re not things that hinder progress elsewhere):
there’s just a type error i have to deal with when it comes to error handling, and everything should be good! i’ll move on to more site prototyping since i have a dummy database to test things against.
update:
i’ve moved all of the logic to use cheerio and worker threads! i tried running just build, and… of course, it didn’t work. a few of the issues were easy to diagnose: i forgot to create a directory on startup, or put .js instead of .ts as a dynamic import path, or potentially had multiple concurrent writes that could occur.
but now, i’m encountering an issue where my requests don’t want to transfer, which i initially assumed was because the object had too many layers.. but trying structuredClone on a similar object works.
for other work:
I LOVE METAPROGRAMMING
okay so for some background i worked on moving the getPages method of the goBILDA adapter to a worker thread method (which parses the goBILDA sitemap to get the main category pages), but i realized from the type error that occurred that the whole type inference debacle got messed up as there was no url parameter in the method input… so now i’ve unleashed a generic-type guard-inference inferno. typescript is a turing complete language combined with a turing complete type system… but at least there aren’t any generics explicitly declared within the code, nor any as keywords sprinkled through this section!
got busy this past week with a bunch of other duties, so slow progress.
i set up a thread pool class with a promise API and type safety… which is kinda overkill but that’s a bit of the whole goal with this project :p but so far i offloaded initially parsing the web page for a <meta http-equiv="refresh"> and determining the link. which is not a long task, so not a huge main thread blocker! but it would allow me to cement the code structure with the worker pool.
the thing was, i really wanted to optimize this build, and the useCache hook i wrote only supported JSON stringifyable values, which means no array buffers. the fetch API allows you to get the response directly as an ArrayBuffer, which, once you mark as transferrable, means that a thread can work directly on the buffer without having to copy all of the data. and since memory contention is one of the two problems i’m trying to solve here… that’s incredibly useful.
the thing was, i couldn’t just insert it directly into the JSON cache entry since it would be parsed by JSON.parse as a string, so i set up another content-addressed directory (cache/blobs instead of cache/files, where most things go) and modified the types to ensure that it was still type safe.
turns out… making that type work out in the function was a PAIN,, especially considering i had generics involved. i was able to work through it with an atrocious amount of as keywords sprinkled throughout, but it’s not very elegant.
in terms of the site, i opened up figma and started working on some prototypes! i’ve been meaning to learn figma and figured this was the perfect opportunity to go through the process. i love how, for the most part, everything is really intuitive. but the “frame” and “group” and “component” concepts being separate things was really confusing to me.
i’m really tired and about to lose my train of thought but for tomorrow i’ll work on porting the rest of the DOM processing over to the worker threads and to cheerio. i’ll also probably have to rethink how code is organized because now the “adapter” is split across two files. there’s definitely a way to make things more elegant, though.
many OOMs and attempts to use chrome devtools later… i think tomorrow i’m going to switch out everything to use cheerio instead of JSDOM to reduce memory usage, then maybe add a thread pool while i’m at it.
currently i’ve just bumped the “old memory” limit up to 16 WHOLE GIGABYTES…. gah javascript..
i’ll also have to debug a JSON parsing problem with a few of the pages. i have no idea what it might be aside from JSON.parse being finicky or JSON.stringify not properly escaping but i highly doubt that’s the reason.
so i tried caching webpages and… um… whoops.
there are so many footguns with concurrency… i had to modify a record to also store a promise chain so that writes didn’t happen concurrently…
and i had run out of memory on a rebuild with cached elements which i assumed was because i was thrashing the filesystem by immediately closing file descriptors once things were read, so i set up an object to keep the file handles around but because of the async boundary they had gotten garbage collected…
urgh
wow i haven’t logged in a while!
the past few days were the last bits of school, so not a lot of work done per day.
i’ve decided to change up how the partdb is built, since i discovered unplugin-macros which really helps streamline build-time logic and the final export of the package. combined with tsdown the whole build situation of the package is a lot cleaner.
in addition, i added filesystem-level caching so that when this eventually gets built on github actions it will have a cache folder that it can use, and with proper expiration handled. i still have to fine tune what computations get cached, but soon hopefully cached builds won’t take the heat death of the universe to complete! since the caching is time stamped it also makes garbage collection of the cache trivial and runnable in a github action. that code isn’t written yet but isn’t too hard.
for that reason i also bumped up the node version because i wanted to use Temporal to deal with timestamps and durations :)
in terms of other minor things:
site package was added, with a template astro site.just build recipe functionali’ll get out of my meta-work slump soon, soon!!
not much to update on, but i decided to clean up the commit history with gitmoji. shout out gleamlins for showing me gitmoji! i wanted to implement it for this project because i think it’s really neat and cute!
there are probably still some stylistic discrepancies/wrong things within the commit history but for now my perfectionism is satisfied :)
VICTORY!!! from here it’s a matter of refining the scraper to make sure there are no duplicates / useless parts / specs are good. the final pieces of the puzzle were removing JSDOM caching to reduce memory usage (since i already check for duplicates reasonably well before the request is made), adding some more filtering for irrelevant products, manually garbage collecting with global.gc() (!!), and allowing for errors in product parsing. i could probably make it more robust by forcing all the categories to be evaluated before products.
i crashed node 😭😭😭
the final pieces were adding lists of already-visited websites, and rate limiting myself so i don’t crash goBILDA. whoops….
i need to break up writes to the file in smaller pieces. i’ll probably write per product processed.
and since things are bound to go wrong with a scraper there’s probably some exception to the rule with some random product that doesn’t make it in the scraper / isn’t processed correctly.
alright, final bit of work for today. since the last log, i put some more logging messages and files to diagnose different issues. i caught one where sometimes goBILDA didn’t redirect through an HTTP code but rather through <meta http-equiv="refresh"> which was annoying. and some category pages had a link to other category pages given the SAME SET OF ATTRIBUTES AS PRODUCTS, or took from an entirely different place in the DOM because the product list is CLIENT SIDE RENDERED. i’m sorry but i am NOT about to pull out puppeteer for a (presumably) 1,000+ SKU website.
i also added a cache to fetched URLs so that i didn’t bombard goBILDA with duplicate requests as i worked. also on that note, i put in a custom user agent while i was at it so that they hopefully don’t block what i’m doing 😊
tomorrow i’ll also probably work on deduplicating DOM queries by keeping track of a list of scanned product pages and filtering out pages that are irrelevant for the catalog’s purposes.
we are getting somewhere!!! now a couple of more rounds of bug fixing and i should have all the metadata for products for goBILDA! from there it’s manual overriding and filtering to remove bundles and anything of that sort.
hey everyone! i’ve been thinking through this project in notes (and in my head) for a little while now, and just got started putting things down in code starting friday or so.
i’m working on fabrica, an overengineered parts library/search engine for robotics teams. currently i’ve structured out how to code static database generation, decided on repository structure, and scaffolded tooling and helper functions for later use, and am now working on a data scraper for goBILDA parts. it’s not the fancy website stuff, but getting all of the data was something i wanted to get down first.
this is a passion project, and am taking my time with everything so i’ve turned off AI features.
so over the 2025-26 season i’ve had to deal with the onshape for ftc plugin and left unsatisfied. its search performance was shoddy, it occasionally went out, the parts library was limited, and there was unintuitive duplication for parts like channel and shafting. as i explored the frc cad ecosystem, i came upon MkCAD, and now FRCDesignApp. i liked how it was open source, the search function worked a lot better, yet… i felt like it was inadequate of a solution.
why not frcdesignapp? well, because i wanted to make one that generates the database at build-time, i wanted semantic type-as-you-search, and i had ambitions to—well, aside from having it under the ultro moniker—integrate it with a future inventory system. i also wanted it to be written in full stack typescript rather than a python backend, since it would allow for further front-end optimizations like SSR and asynchronous data streaming with type safety.