Spaces:
Running
Running
Commit History
19 days ago
-
fix: src/catalogue.js
Verified signature6f13fff -
fix: src/ImageField.jsx
Verified signature17b566c -
fix: src/merge.js
Verified signature6dddbb3 -
fix: src/product-search.js
Verified signature01be3a2 -
fix(neon): shrink ring stroke to 1px and halo to 2.5px (half width)
Verified signaturea6f73b7
20 days ago
-
refactor(css): drop the duplicated ringed-tile override; neon-frame.css owns it
Verified signature87f3556 -
refactor(neon): own the ringed-tile dim override here, so it has one definition
Verified signature558f1dd -
fix(share): an avatarimg record with no usable id gets no share URL, not the dead product route
Verified signature90b5ae8 -
fix(neon): run the neon light around the picture frame instead of rotating the mask
Verified signature0d446a1 -
refactor(css): import the neon frame from its own module so a rebuild keeps it
Verified signature703cbe5 -
Delete src/neon-frame.css
Verified signaturefdb0de1 -
fix(neon): run the neon light around the picture frame instead of rotating the mask
Verified signature8f04110 -
fix(share): route avatarimg photographs to /share/i/ instead of /share/p/
Verified signaturedf9d078 -
PhotoViewer: a vai-avatar photograph is not a product, even though it carries a sku
Verified signature`isProduct` drove the alt text, the gallery check, the price/spec caption and the detail title, and it was keyed on `Boolean(photo.sku)`. avatar-interiors.js gives every photograph published on vai-avatar a namespaced `sku` (`avatarimg:<id>`) so it travels the same code path as a catalogue product, so all of these photographs were announced as "Sản phẩm (avatarimg:b68dc779370dc70e)" and rendered with the product caption. `avatarImageId` distinguishes them: present only on records read from the vai-avatar interior index. detailOf() derives the same flag, and the two now agree because they read the same field.fbd38cf -
detailOf: a vai-avatar photograph is not a product, even though it carries a sku
Verified signatureavatar-interiors.js gives every photograph published on vai-avatar a namespaced sku (`avatarimg:<id>`) so it travels the same code path as a catalogue product: same viewer, same panel, same neon. That is worth keeping, but `Boolean(record.sku)` cannot tell the two apart, so every such photograph rendered as a product: the heading was the literal `avatarimg:b68dc779370dc70e`, the description came from an empty spec sheet, and no room, category or tag was shown even though the record carries all of them. `avatarImageId` is what actually distinguishes them -- it is present only on records read from the vai-avatar interior index. Keying off it renders the photograph as what it is, and leaves the shared viewer and shared `sku` untouched.4d8e264 -
shareURL: send a vai-avatar photograph to /share/i/, not /share/p/
Verified signatureavatar-interiors.js gives every photograph published on vai-avatar a namespaced sku (`avatarimg:<image_id>`) so it travels the same path as a catalogue product: same viewer, same detail panel, same neon, same share anchor. That sku is not an identity the product catalogue knows, and shareURL() preferred it, so every one of those photographs was sent to `/share/p/avatarimg%3A...` -- a route that resolves only against the product catalogue. It answered "Not Found" for the whole set, on the tile's share button and on the viewer's "Xem trang" alike. `avatarImageId` is present only on records read from the vai-avatar interior index and is the raw id, which is what `/share/i/` expects, so the interior identity is now resolved first and unconditionally. No prefix has to be stripped, and it cannot drift from whatever namespace merge.js happens to use for tiles. The namespaced sku stays on the record: it is what keeps the photograph on the product code path inside this app. The bug was only t…7c7b7e4 -
Neon: run the light around the frame's perimeter instead of spinning the ring layer
Verified signatureThe ring was a full-size ::before carrying BOTH the conic gradient and the 2px border mask, and the animation rotated that whole layer. The mask rotates with it, so on a rectangular tile the painted border no longer matched the tile's own edges -- the highlight appeared clipped, skewed and partly off the frame. On a near-square tile it happened to look right, which is why it read as "a static neon border" rather than as a bug. Rotate the gradient's angle instead, and leave the layer still. @property is required so the angle is interpolable; the animation is gated behind @supports at-rule(@property) because an unregistered custom property would jump discretely at 50% and read as a strobe.83cbb13 -
Restore viewer CSS, ring only the newest ten, and clear the search pill
ML Intern1. CSS. The previous commit stripped the size/position controls with a script that cut from .field-controls to the end of the file, taking 147 lines with it: every .photo-* rule, .search-status, .search-progress and .footer. It went unnoticed because an unstyled <dialog> still opens, still shows the photograph, and never throws -- it just renders a raw UA box with the share toolbar stacked as plain text. Restored verbatim from the last good revision; app.css goes 17.1kB -> 24.7kB. 2. Ten rings, not sixty. RECENT_LIMIT (shown) and FEATURED_LIMIT (ringed) are now separate constants, because "show more photographs" and "only the newest glow" pull in opposite directions if one list drives both. 24 rings on a dense field is decoration rather than emphasis. The rest still render -- as ordinary tiles, which is what the brief asks for and what makes the ten legible. 3. More photographs: fieldCount 96/56 -> 150/96. Raised less on phones because tile area is bo…f134575 -
Ring every exact hit with running neon, and sync vai-avatar's newest photos
ML InternFour changes, all of them visible at a glance: 1. The neon outline now RUNS. It was a static ring with a breathing glow; it is a conic gradient masked to a 2px border and rotated every frame, so the light travels around the edge. An animated border-color cannot do this (borders take no gradient) and an animated box-shadow only pulses in place. The stroke is on ::before and the halo on ::after, because one pseudo-element can hold only one background and the ring needs both. mask-composite turns the full-size gradient into an outline; without it the gradient would fill the tile and hide the photograph, so the test asserts the mask is present. 2. Interiors published on vai-avatar now appear here. src/avatar-interiors.js reads bep40/vai-avatar2-interior/index.json, which is the file carrying created_at -- 153 rows, newest 2026-10-05. metadata.json has 1,752 rows but no timestamps at all, so it cannot answer "newest". These are matched by TEXT, not by th…eb92bc7 -
V.AI STUDIO backdrop, vai-avatar's product search, size/position, neon
ML InternFour changes, all on the search field. Background. The same artwork vai-avatar shows on its body (public/avatars/vaistudio_center.png, copied byte-for-byte, md5 checked) at 30% opacity. It is a separate fixed layer rather than a background-image on .app because opacity on .app would fade the photographs and the search pill with it, and background-attachment:fixed cannot compose with the .vignette and .grain layers already sitting on top of the field. Product search. Ranking is now vai-avatar's own scoreProduct(), ported to product-search.js rather than extended in place, so the two modules can be diffed against each other when the original changes. The whole rule set carries over: exact SKU/model/title must win outright, millimetre sizes are compared numerically (the specs write "900 mm", "900mm" and "W900" for the same shelf), material words are matched against specs as well as title, and brand names get a flat bonus. 35 tests in scripts/test-product-search.mjs pin each rule. Both s…0df020e
22 days ago
-
keep the share button's label on a phone screen
ML InternChanging the control from <a target="_blank"> to <button> to reach navigator.share kept its class name, and the class carried a rule written for the anchor: at 560px and below, .photo-share span { display: none }. On a phone that left a 44px button with no visible label and no accessible name, since display:none removes the text from the accessibility tree too -- and the label is where the "Đã chia sẻ" confirmation appears, so the phone could not see the share succeed. Clipped out of sight instead: the span keeps a 1px box and clip-path: inset(50%), which is the standard visually-hidden pattern. The text stays in the accessibility tree, so the button is still named, and the confirmation still updates -- it just is not drawn on a screen with no room for it. Measured on the built app at 1280px and on a Pixel 7 viewport: button is 44x44 in both, the span computes to position:absolute with clip-path:inset(50%), and a click still reaches navigator.share and rewrites the label to "Đã chia …1d2e122 -
viewer: share through the platform's own sheet, and drop the origin
ML InternTwo changes to the same panel, one reason: the viewer was a place to look at a photograph and to leave the app, and it was doing neither job well. The share control was an <a target="_blank">, which opens a browser tab. That is not sharing. navigator.share is where the platform's share sheet lives, and on a phone it is the sheet with the reader's real apps on it -- Messages, Zalo, Telegram -- which no anchor can reach. It is now a <button> that calls navigator.share with the photograph's canonical URL and title, falling back to the clipboard where share is missing (desktop Firefox, older Safari) or the permission is refused, so the action never dead-ends. The confirmation goes into the button's own label for 1.8s rather than into an alert(). The viewer is full-screen; a modal dialog stacked on top of the photograph is a worse outcome than the share failing quietly. A dismissed sheet rejects with AbortError, which is a reader saying no rather than an error, so it is caught and ignored…cd00894 -
note why detail.description cannot gate the panel
ML InternMeasured rather than assumed: adding it to hasDetail takes ambient tiles from 0 to 180 shown, and every one of them would hold only the single fixed sentence describeInterior falls back to. All 1,222 real interiors already pass the gate on their own metadata, so nothing is gained.9c69ff8 -
Share link on every photograph, a real description, no source button
ML InternThree things the viewer was missing, all reported from using it. A share link on each photograph in the field. Every tile now carries one, pointing at the same public address the viewer's toolbar link does: /share/i/<id> for an interior photograph, /share/p/<slug> for a catalogue product. All 1,222 interiors and all 11,704 products resolve to one; the 180 ambient tiles in collection.json resolve too, because they carry ids. Hidden until hover, except the top three results, which show theirs outright -- 150 photographs each wearing a button turns the plane into a control panel. The button is aria-hidden and tabIndex -1, nested inside the tile's <button>. Nesting an <a> in a <button> is not valid HTML, so it is a pointer affordance only: the reachable share is the named link in the viewer. Clicking it opens a new tab -- navigating away would destroy the search, and the field has no route to come back to. Getting it clickable took three fixes, each measured rather than guessed: The sh…4641f85 -
viewer: name, describe and link every photo the search finds
ML InternOpening a photograph in the viewer showed nothing but the picture: no title, no room, no categories, no link back to the article it came from. The record carried all four -- worker.js spreads the whole image-embeddings.json row into each search hit, so title, categories and source arrive with the photograph. But nothing downstream ever kept them. ImageField creates one DOM node per photograph and reuses it across searches; the node's onclick closed over the record it was built from, and on first paint that record is the ambient entry from collection.json, which holds only id/url/ar/blur. A node's position was refreshed on every layout pass, its record was not. So any photograph that had been on screen before you searched -- 180 of the 1,222 -- kept opening with none of its metadata, no matter that it matched and now carried a title and a source. Layout now writes node.image alongside node.target, and the click handler reads it at click time. Adds src/detail.js, restored from ffe2656 …29ed8a3
23 days ago
-
feat: product share links + repair Malloca/Hafele misbranding
Verified signatureTwo things. Share links. vai-avatar served share pages for interior images and chat answers but nothing for products, so a product link had nowhere to point. A /share/p/<sku> route now exists there and resolves against VAIX_PRODUCTS, the 11,704-row catalogue that Space already loads. A "Xem thêm" button in the viewer toolbar links products to it and interiors to the existing /share/i/. The link is keyed on the photo, not the image, so paging a gallery still shares the product rather than one of its photographs. Misbranding. 99 rows are named "Malloca | ..." while their brand field says Hafele, and all 1,011 of their image URLs point at Hafele's CDN rather than the Malloca storefront. Hafele resells Malloca, so these are Malloca products catalogued under the wrong brand. 77 now show a real Malloca photograph and the remaining 22, which have no Malloca row anywhere in the file, keep their photo with the brand corrected. No product is dropped and no non-Malloca row is touched. Matching…9bf56b3 -
fix(catalogue): stop dropping 355 Malloca products that are not duplicates
Verified signature3fa0a11 -
feat(catalogue): load the full 8,162-product shop, not just pasted URLs
Verified signaturea301301 -
feat(field): merge Malloca products into the interior image field
Verified signaturee34a847 -
fix(model): pin the CLIP text tower to a loadable ONNX revision
Verified signatureFixes 'Không tải được bộ tìm kiếm' / [ShapeInferenceError] Inferred=1 Declared=768. The model repo's first ONNX export declared 'masked' as [...,768]; commit 9e1d535 re-exported it cleanly. transformers.js cached the broken bytes under the unversioned /resolve/main/ URL, so reload and 'Thử lại' replayed them. Pinning 'revision' changes the cache key and lets those browsers recover.ad2fb72 -
feat(catalogue): read vai-avatar datasets directly so add/edit/delete sync without a writer
Verified signatureThe catalogue could only ever be as fresh as the last commit to this Space, and the only writer (the reconciler in the vai-avatar Space) stays disabled until that Space is given a write token. huggingface.co now reflects the caller's Origin in Access-Control-Allow-Origin on both the /resolve hop and the /api/resolve-cache hop it redirects to, so this Space can read the two datasets cross-origin and needs no token at all. fetchJSON() now tries the live dataset first and keeps the committed mirror as a same-origin fallback, so a Hub wobble cannot empty the shop and a stale mirror is no longer a ceiling. Every candidate is parsed and shape-checked, so a truncated or HTML body cannot pass as a catalogue. App.jsx polls every 30s (was 90s), which is now the whole end-to-end delay.e85692d -
fix(catalogue): build the product bundle + stop Vite mangling the mirror URL
Verified signatureThe Space served index-CigEzVrZ.js, a build from a tree with no product code at all, so no product could ever appear in the photo-viewer. Rebuilds from current src/ and points index.html at the stable assets/index.js + assets/app.css. candidateURLs() built its candidates with new URL(`../../${path}`, import.meta.url). Vite globbed that directory at build time and replaced the expression with a map keyed by the literal `../../${path}`, so every lookup was undefined -> 404, and the build dragged README.md, package-lock.json and an 11 MB products-gallery.json into dist/assets. The base is now a plain string; the rewrite cannot match it.dc5d935
24 days ago
-
Upload src/App.jsx
Verified signature44a97d8 -
Upload src/catalogue.js
Verified signatureeccb901 -
docs(catalogue): correct a claim that contradicted the README
Verified signatureThe header comment said "products-index.json is mirrored here for the same reason". Nothing in src/ reads that file: assets.js only exposes DATASET (the interior-photo dataset) and worker.js only fetches image-embeddings.json plus the .bin matrix. It is also not mirrored — sync-catalogue.mjs writes exactly two files — so the comment described a mechanism that does not exist. Rewritten to say what actually happens: only products.json and promos.json are mirrored, and the ~6k base Malloca catalogue is deliberately not, because it is only drawable from the Parquet. Also pinned down one thing the old wording left open: a record added via the promo CMS is added under `p.sku || p.model` in promos.json ONLY — it never appears in products_url_added.json. That is why the alias table has to span both files.2bda94f -
Upload src/catalogue.js
Verified signatureb0ed920 -
Upload src/catalogue.js
Verified signature8cbd471 -
Upload src/catalogue.js
Verified signaturec0a8e90 -
Upload src/catalogue.js
Verified signature0c12813 -
Upload src/App.jsx
Verified signatureaa815f9 -
Upload src/catalogue.js
Verified signatured5b24a6 -
Upload src/catalogue.js
Verified signature5b2024b -
Upload src/catalogue.js
Verified signature784ab80 -
Name the likely CORS cause when a live catalogue read fails
Verified signaturea3b10ae -
Restore full PhotoViewer (undo accidental truncation)
Verified signature2c4780a -
Upload src/PhotoViewer.jsx
Verified signatured0d6710
25 days ago
-
Fix product gallery paging: resolve the displayed frame from position
Verified signature6d8d43b -
Upload src/PhotoViewer.jsx
Verified signaturedbf4bb9 -
Upload src/catalogue.js
Verified signature2e3a635 -
Upload src/catalogue.js
Verified signature1bba59d