End-of-session bundle 2026-05-26 covering ~10 distinct threads atop the A.7.5-polish-8 sky-wheel mini-portal commit (9cdd2cd). A.8 room.html sprint deferred per user — waiting on image scraping for RWS + future decks so the room can apply the image-mode pattern uniformly w.o. straddling text-mode fallback for unequippable Earthman Shabby Cardstock.
**(1) Game Kit + My Wallet applet realignment** — user spec "this isn't a place for tokens" / "only equippables should be there". Game Kit applet (/gameboard/, _applet-game-kit.html) drops the Free Token block — only PASS/BAND/CARTE/COIN trinkets + decks + dice remain. Free + Tithe tokens MOVED to the My Wallet applet on /dashboard/ (_applet-wallet.html rewrite). All trinkets COPIED into Wallet w. same .tt tooltip + DON/DOFF wiring so the user can equip from either surface. Stacked free/tithe icons (single icon per type) carry a .shop-badge ×N count (fa-coins for free, fa-piggy-bank for tithe — the latter standardized from outlier fa-hand-holding-dollar, now matching wallet / kit_bag / shop seed / FTs). Writs placeholder gets the same .token + .tt chrome ("Base currency unit ; Earned at the gate, spent in the shop"). 99+ cap on all badges. home_page view in apps/dashboard/views.py now passes pass/band/carte/coin + free/tithe tokens + counts + equipped_trinket_id. gameboard.js loaded on dashboard for the hover-portal tooltip system; #id_game_kit wrapper added (uses display: contents to stay transparent to the section-grid layout). Standalone game_kit.html page (_game_kit_sections.html) also reorganized — trinkets/tokens/decks each use bare .token icons w. centered flex row + 2rem gap, 1.5rem font-size to match gameboard sizing. id_game_kit outer wrapper data attrs (equipped-id, equipped-deck-id, in-use-deck-ids) feed buildMiniContent() for Equipped/Not Equipped/In-Use status.
**(2) My Sea label + shadow polish (my_sea.html Cross + applet)** — user spec "labels appear below and beneath the card, w. the card's shadow obscuring the very top of the label" per the GRAVITY/LEVITY .sea-stack-name pattern. .sea-pos-label repositioning: CROWN + COVER ABOVE slot (bottom: 100%; translate(-50%, -0.4rem)), LAY + CROSS BELOW slot (top: 100%; translate(-50%, 0.3rem)), LEAVE + LOOM increased breathing room (translate -0.4rem LEFT / 0.4rem RIGHT — was 0.1rem overlap). CROWN cell translateY(-0.5rem) UP + LAY cell translateY(0.5rem) DOWN for COVER/CROSS label breathing room. Filled-card downward shadow chain (1px 2px 0 black, 0 4px 0 black-faint, 2px 5px 5px black-blur) scoped to .my-sea-cross .sea-card-slot--filled only — empty dashed placeholders stay shadowless per user spec ("only the cards that replace [slots] should [have shadows]"). Four rotation-correction overrides for box-shadow rotating w. element transform: base (0deg), reversed (180deg sign-flip), cross (90deg matrix rotation → 2px -1px), cross+reversed (270deg → -2px 1px). Saved here for future reference since the matrix derivation is non-obvious: CSS rotate(θ) CW maps offset (a, b) → screen (a·cos θ − b·sin θ, a·sin θ + b·cos θ); solving for unrotated offsets that produce screen-down-right post-rotation gives the 4 chains. My Sea applet .my-sea-slot-label (z-index 0, margin-top 0.15rem) + .my-sea-slot--filled shadow + reversed-variant shadow inversion all mirror the page treatment.
**(3) DEL btn + FLIP btn state machine** — user spec: DEL un-disables as soon as ANY card drawn (was gated on hand_complete) ; FLIP btn .btn-disabled + text swap to × once hand complete. _setComplete(on) toggles FLIP btn class + label (parity w. DEL convention: × disabled / word active) ; new _setHasDrawn(on) helper extracted (was bundled in _setComplete). Wired into 4 transitions: (a) manual deposit _filled === 1, (b) initial page-load seed when _filled > 0, (c) AUTO DRAW path post-POST (CRITICAL FIX — was missing, only manual deposit synced DEL even though server already committed all cards on AUTO DRAW), (d) _resetHand spread-switch reset. Template DEL btn gates on saved_by_position (any draw); FLIP btn gates on hand_complete. Test test_partial_hand_del_btn_carries_btn_disabled inverted to test_partial_hand_del_btn_is_enabled per the new spec.
**(4) Sig-change MySeaDraw RESET (cooldown loophole closure)** — user-reported revenue-stream loophole 2026-05-26: switching sig used to re-open the FREE DRAW gate + forfeit any paid-draw credit, because apps/gameboard/views.py:266's `in_cooldown = active_draw is not None` keyed entirely off the MySeaDraw row's existence (NOT off User.last_free_draw_at, which is the cooldown TIMER but doesn't drive the in_cooldown decision). Initial draft DELETED the row on sig change — turned out too aggressive: lost both the cooldown anchor (created_at via the active_draw check) AND the paid-state fields (deposit_token_id, paid_through_at). FIX: save_sign on actual sig change `.update(hand=[], significator_id=new, significator_reversed=new)` — preserves cooldown + paid revenue, just resets the hand + sig snapshot. clear_sign left untouched (sig-cleared user can't draw anyway per my_sea_lock's no_significator guard; row sits dormant until re-pick routes through save_sign's reset). Guarded w. sig_changed so re-saving the same sig is a no-op. User.last_free_draw_at was always safe — User-level field, only ever set in my_sea_lock, never cleared (user confirmed the Brief shows 11:59pm consistently). Subtle architectural note for future: the in_cooldown decision being row-existence-based rather than timestamp-based is the load-bearing implicit dependency this loophole exposed; any refactor that delete()s the row needs to either flip in_cooldown to consult last_free_draw_at OR preserve the row as we did here.
**(5) Kit-bag DOFF async refresh** — user-reported 2026-05-26: deck disappears entirely from kit-bag on first DOFF; only manual page refresh restores the placeholder. Root cause: _syncKitBagDialog() in gameboard.js did card.querySelector('i') for the placeholder icon — worked for trinket/token cards (single FA <i>) but BROKE for image-equipped decks whose card-stack icon is <svg class="deck-stack-icon"> (no <i> to copy → empty placeholder div). DROP the client-side optimization, route both DOFF paths thru _refreshKitDialog() (symmetric w. DON). Single source of truth = server-rendered _kit_bag_panel.html's placeholder branch (re-renders _deck_stack_icon.html w.o. the deck arg for the empty-fill SVG).
**(6) Sky-wheel planet circle shadow** — user spec "tight 1px 1px black shadow at opacity 0.7 on planet circle groups in all sky locations". Base `filter: drop-shadow(1px 1px 0 rgba(0,0,0,0.7))` on .nw-planet-group so planet badges lift off the wheel rings on /dashboard/sky/ + My Sky applet + any future surface. Hover/active state chains shadow + glow ("drop-shadow ... ; drop-shadow(0 0 5px primary-lm)") since CSS filter REPLACES rather than APPENDS — shadow has to be re-stated on the hover rule to persist during interaction. Elements/signs/houses groups keep their glow-only hover (the request was planet-specific).
**(7) TarotCard suit_icon + Fiorentine additive numerals** — (a) suit_icon property pre-checks for major arcana trump 0 → fa-hat-cowboy-side (Fool/Nomad/Matto archetype) and trump 1 → fa-hat-wizard (Magician/Schizo/Bagatto archetype), pinned BEFORE the self.icon branch so even a deck seed supplying a different icon for these ranks normalizes to the convention. Earthman's seed already aligns; Minchiate (empty icon field) used to fall thru to fa-hand-dots. (b) _to_roman() adds _FIORENTINE_ADDITIVE_NUMERALS = {4:'IIII', 19:'XVIIII', 24:'XXIIII', 29:'XXVIIII', 34:'XXXIIII', 39:'XXXVIIII'} pre-check — locked-in 6-exception list per user-corrected spec (initial draft used universal additive form, user clarified "no, only these specific ones, e.g. trump 9 still prints IX + trump 14 still prints XIV per the actual Minchiate deck art"). +2 regression tests: additive overrides + non-overridden subtractive (9=IX, 14=XIV, 44=XLIV, 49=XLIX).
**(8) Gear menu NVM font fix** — _my_sea_gear.html's NVM btn changed from <a class="btn"> to <button onclick="location.href=..."> per [[feedback-btn-vs-anchor-font-family]] (anchor inherits body serif font; button stays sans-serif by browser default). Brief's NVM uses <button> + reads correctly — this matches it.
**(9) Image-mode slot transparency overrides** — 3 surfaces got `overflow: visible` (base overflow: hidden was clipping the contour-stroke filter chain) + transparent bg/border re-states for image-equipped Minchiate cards on (a) .my-sea-cross .sea-card-slot--filled + image variant, (b) .sig-stage-card.sea-sig-card.sig-stage-card--image base + levity-polarity nested override, (c) .sea-deck-stack--single .sea-stack-face:has(.sea-stack-face-img) (using :has() to key off the conditional back-img child). Followup to A.7.5-polish-* sprint — those surfaces' image-mode bg overrides didn't include overflow.
Tests: 1336/1336 IT+UT total green (was 1322 before the session). No FT runs per [[feedback-ft-run-discipline]]; visual verify ongoing by user across the session via Firefox reload.
Code architected by Disco DeDisco <discodedisco@outlook.com>
Git commit message Co-Authored-By:
Claude Sonnet 4.6 <noreply@anthropic.com>
User-spec 2026-05-25 PM ("To the #id_sky_tooltip, whenever it has a DON|DOFF apparatus, we should add a #id_mini_tooltip_portal except, instead of Equipped|Unequipped, this would feature an Aspected|Unaspected toggle"). Mirrors the game-kit / wallet Equipped-Unequipped micro-tooltip pattern — text-swaps "Aspected" / "Unaspected" tied to sky-wheel's `_aspectsVisible` state.
**(1) `sky-wheel.js`** — 3 new helpers (`_updateAspectMiniPortal` / `_showAspectMiniPortal` / `_hideAspectMiniPortal` / `_positionAspectMiniPortal`) + element cache (`_miniPortalEl`) + 5 integration points (cache in `_injectTooltipControls`; show in `_activatePlanet` + `_activateAngle`; hide in `_activateElement` + `_activateSign` + `_activateHouse` + `_closeTooltip`; text-swap in `_updateAspectToggleUI`). State derives from existing `_aspectsVisible` global — single source of truth, no parallel tracking. Only the planets + angles rings show the apparatus (per existing UX); the elements/signs/houses rings hide it w. the rest of the DON/DOFF buttons.
**(2) Positioning** — mirrors `gameboard.js:285-287`'s right-anchored pattern (was left-aligned + 6px gap in the first draft): pin mini-portal RIGHT edge to main tooltip's right edge, 4px below the tooltip's bottom. Text width changes grow/shrink leftward — same visual logic the Game Kit's Equipped/Unequipped already uses.
**(3) z-index** — set to 150 inline via JS for the sky surface (default `#id_mini_tooltip_portal { z-index: 9999 }` from `_gameboard.scss` is universal — too high for the sky tooltip's PRV/NXT buttons, which inherit the tooltip's z-index 200 stacking context). User-reported "make sure its z-index falls behind the NXT button, as now it's in front of PRV". The sky tooltip body itself sits at z-index 200; mini-portal at 150 falls below it where they overlap (they don't — the mini sits below the tooltip body) but lets the absolutely-positioned PRV/NXT btns inside the tooltip render on top.
**(4) Styling** — bumped `#id_mini_tooltip_portal` font-size 0.8em → 0.95em + added `padding: 0.35rem 0.75rem` + `border-radius: 0.3rem` per user-spec "a bit bigger both in dimensions and font-size". Universal change (affects game-kit + wallet mini-portals too) — visually closer to the main tooltip's text scale w/o approaching it.
**(5) Dashboard parity** — `dashboard/home.html` gains the same `<div id="id_mini_tooltip_portal" class="token-tooltip token-tooltip--mini">` scaffold so the My Sky applet (`_applet-my-sky.html`) picks it up. Without this, the applet's sky-wheel rendered the main tooltip but the mini-portal `getElementById` would return null. Now both the standalone /dashboard/sky/ page + the dashboard's My Sky applet host the same mini-portal scaffold; sky-wheel.js caches whichever one is present on init.
Tests: 1314/1314 IT+UT total green (76s; pure SCSS + JS + template changes, no test surface — no new conditional or template branch to test directly). Visual verify on /dashboard/sky/: Saturn planet tooltip opens w. DON visible + "Unaspected" mini-portal below-right; click DON → text swaps to "Aspected" + aspect lines draw on wheel; click DOFF → swaps back.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- html { font-size: clamp(14px, 2.4vmin, 22px) } — single sliding scale; everything in rem (sidebar widths, h2 font-size, paddings) scales together. Phone rotation swaps width/height but vmin stays the same → 1rem stays the same → navbar/footer/h2 hold their size between portrait + landscape.
- :root --sidebar-w: 5rem (replaces the locally-scoped $sidebar-w SCSS var that lived inside @media blocks); --h2-col-w: 3rem for the rotated wordmark column in landscape. var(--sidebar-w) + var(--h2-col-w) are the only knobs that move the layout.
- Landscape container: margin-left = calc(var(--sidebar-w) + var(--h2-col-w)); margin-right = var(--sidebar-w). Applets are now clipped INSIDE the h2 column, so the rotated "BILLPOST" / "DASHBOARD" wordmark never has content bleeding behind it (the original complaint).
- h2 markup refactor across 13 templates: <span>BILL</span><span>POST</span> instead of <span>BILL</span>POST. Portrait styling: display: flex; first span flex 0 0 45% + --quaUser colour; second span flex 0 0 55% + --secUser inherited. Per-span text-align: justify + text-justify: inter-character keeps the inter-letter spacing within each span. Landscape resets the flex (single rotated wordmark, not split).
- Drop the four h2 font-size jumps (min-height: 400/500/800px) — single font-size: 3rem now scales fluidly via root rem. Drop the @media (orientation: landscape) and (max-width: 1100px) h1 override (rem-fluid handles cramped widths). Drop the entire @media (orientation: landscape) and (min-width: 1800px) sidebar-doubling block in _base.scss / _applets.scss / _bud.scss — the rem clamp ceiling already caps the size.
- _bud.scss + _applets.scss: bud-btn / bud-panel / bud-suggestions / gear-btn / applet menus all switch to var(--sidebar-w)-based positioning; landscape rules are single (no per-breakpoint duplication).
- Per-spec tradeoff: non-.btn-primary buttons (BYE / NVM / OK / kit-btn / etc.) inherit rem-fluid like everything else and will scale slightly w. viewport. User explicitly OK'd this — they don't need to stay px-fixed.
- 852 ITs + 24 layout/navbar/bud FTs green; existing geometry assertions are relative or categorical (not exact-px) so the rem clamp doesn't surface failures at the 800x1200 FT viewport.
Code architected by Disco DeDisco <discodedisco@outlook.com>
Git commit message Co-Authored-By:
Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Wires the C2 Brief model into the existing Note-unlock pipeline. Per the per-category Post model: each user has a single Post(owner=user, kind=NOTE_UNLOCK) titled by the header Line "Look! — new Note unlocked"; each unlock appends a per-event Line ("Stargazer, 5:21:00 PM") + spawns a Brief FK'd to that Line.
Server:
- billboard.Post gains a `kind` enum (NOTE_UNLOCK / USER_POST / SHARE_INVITE, default USER_POST) so the per-category Post is deterministically discoverable via (owner, kind=NOTE_UNLOCK).
- drama.Note.grant_if_new now returns (note, created, brief) — backwards-incompat tuple shape; the new third slot is None on idempotent re-grants. On a fresh grant it: get-or-creates the user's Note Unlocks Post; ensures the header Line; appends a per-event Line w. timestamp; creates a Brief(kind=NOTE_UNLOCK, owner, post, line, title=note.display_title).
- dashboard.views.sky_save's response shape flips {note: {...}|null} → {brief: {...}|null}. The new helper _brief_to_banner_dict exposes {id, kind, title, line_text, post_url, square_url, created_at}; for NOTE_UNLOCK kind, square_url = reverse('billboard:my_notes').
Banner JS (apps/dashboard/note.js):
- Module renamed Note → Brief (Note kept as an alias during the C3 sprint; will retire in C3.e). showBanner now consumes the Brief shape: title from brief.title, body from brief.line_text, time from brief.created_at, FYI href = brief.post_url, NVM dismisses, .note-banner__image is rendered as a clickable <a href=brief.square_url> when square_url is set. The CSS class names (.note-banner, .note-banner__title, etc.) stay so all existing SCSS + FT selectors keep working.
- sky.html + _applet-my-sky.html flip Note.handleSaveResponse → Brief.handleSaveResponse.
Tests:
- new IT class GrantIfNewSpawnsBriefTest (5 tests): first grant creates Post+Line+Brief, idempotent on second grant returns no brief, two different slugs share one Post, line text carries note title, post.kind == NOTE_UNLOCK. PostKindFieldTest (2 tests): default user_post + all three choices present.
- existing drama test_models.GrantIfNew tests updated to unpack the third tuple element.
- dashboard.tests.integrated.test_sky_views: 5 tests flipped from data["note"] → data["brief"] w/ the new payload shape (kind=note_unlock, title=Stargazer, line_text contains Stargazer, post_url under /billboard/post/, square_url=/billboard/my-notes/).
- NoteSpec.js (Jasmine): 12 specs rewritten for the Brief shape — SAMPLE_BRIEF carries the seven fields, T6 asserts the square is a clickable <a>, T8 asserts FYI points at brief.post_url (was hardcoded /billboard/my-notes/).
- functional_tests.test_applet_my_notes: T2 split — FYI now navigates to /billboard/post/<uuid>/ (the post detail / mark-read contract), the .note-banner__image square preserves the legacy jump direct to /billboard/my-notes/.
billboard/0003_post_kind migration auto-generated. 808 ITs + 9 my_notes FTs + 1 Jasmine FT all green.
Code architected by Disco DeDisco <discodedisco@outlook.com>
Git commit message Co-Authored-By:
Claude Sonnet 4.6 <noreply@anthropic.com>
The Post/Line models always read more like billboard tenants than dashboard ones (1st-person personal vs. 2nd-person provenance feed); the upcoming Brief model needs them in the billboard namespace as the canonical surface they FK into. C1 is a pure relocation w. zero new behavior: 789 ITs + 20 sky/Post FTs green against the moved code.
- billboard.models adds Post (owner + shared_with) + Line (text + post FK), schema mirroring the legacy dashboard models 1:1; Post.get_absolute_url now reverses to `billboard:view_post`.
- billboard.forms adds LineForm + ExistingPostLineForm (moved from dashboard.forms; dashboard/forms.py removed).
- billboard.views absorbs new_post / view_post / share_post / my_posts (templates rendered from apps/billboard/post.html + my_posts.html).
- billboard.urls adds the namespaced routes: /billboard/new-post, /billboard/post/<uuid>/, /billboard/post/<uuid>/share-post, /billboard/users/<uuid>/. dashboard.urls drops the corresponding entries.
- _applet-my-posts + _applet-new-post URL refs now use the billboard: namespace; templates/apps/dashboard/{post,my_posts}.html removed.
- api/serializers + api/views + api/tests/integrated/test_views imports flip dashboard.models → billboard.models (PostSerializer / PostDetailAPI / PostLinesAPI / PostsAPI all retain identifiers — the model rename to Brief lands in C2).
- dashboard/tests/integrated/test_{models,views,forms} + dashboard/tests/unit/test_{models,forms} swap imports; test_views URL strings flip /dashboard/post/ → /billboard/post/, /dashboard/new_post → /billboard/new-post, /dashboard/users/ → /billboard/users/, share_post → share-post (path) / billboard:share_post (reverser). Tests stay in dashboard.tests/ for now — relocation TBD.
- functional_tests/my_posts_page.py URL string flips to /billboard/users/.
- Auto-generated migrations: billboard/0001_initial (CreateModel Post + Line), dashboard/0003_remove_post_* (drops legacy Post + Line), drama/0004_alter_gameevent_verb (incidental — choices field caught up).
This commit drops the dashboard Post/Line tables w/o data preservation; user has confirmed staging-side wipe is acceptable. C2 introduces the Brief model + read-tracking + slide-down banner unification. C3 hooks Note-unlock + share-post-invite + magic-link / invalid-link `messages` calls into the new Brief / banner pipeline.
Code architected by Disco DeDisco <discodedisco@outlook.com>
Git commit message Co-Authored-By:
Claude Sonnet 4.6 <noreply@anthropic.com>
A user-typed TZ override fed through schedulePreview's `if (tz) params.set('tz', tz)` path made PySwiss compute the chart against a TZ that didn't match the lat/lon, so a partial edit (e.g. "America/New_Yo|") returned HTTP 400. Mirror the lat/lon convention: tz field gets readonly + tabindex:-1 across all three sky contexts (Dashsky sky.html, in-room PICK SKY _sky_overlay.html, My Sky applet _applet-my-sky.html). Auto-population still works because the JS writes via .value rather than via user input. The <small id="id_nf_tz_hint"> "Auto-detected from coordinates." line is removed; that copy now lives on the <input>'s placeholder so an empty TZ field self-explains. JS purges every tzHint reference (const declaration + 4 .textContent writes per file × 3 files).
SkyViewTest.test_tz_input_is_readonly_and_carries_auto_detect_placeholder pins the rendered Dashsky markup: id_nf_tz carries `readonly`, the placeholder is "auto-detected from coordinates", and `id="id_nf_tz_hint"` no longer appears anywhere. Existing MySkyTimezoneRefreshTest still passes — it asserts the field auto-fills via JS, which still works on a readonly input.
Code architected by Disco DeDisco <discodedisco@outlook.com>
Git commit message Co-Authored-By:
Claude Sonnet 4.6 <noreply@anthropic.com>
My Sky applet (.../dashboard/_partials/_applet-my-sky.html): adds <button id="id_applet_sky_delete_btn" class="btn btn-danger"> at the wheel center, gated on user.sky_chart_data. Click → window.showGuard("Forget sky?") → on OK, fetch POSTs sky_delete (clears every sky_* field on User), removes the 'sky-form:dashboard:sky' localStorage entry that would otherwise rehydrate the post-reload form via _restoreForm(), then reloads — applet's form-render branch is server-template-gated on chart_data so the page comes back form-only.
PICK SKY in-room overlay (.../gameboard/_partials/_sky_overlay.html): adds <button id="id_sky_delete_btn"> at the wheel center. The wheel here is purely a live preview — sky_save fires only on SAVE SKY click w. action='confirm', so there's no draft Character to delete & we do NOT touch the Character/User model. The DEL handler clears the SVG, resets form fields (including lat/lon/tz/tzHint), nulls _lastChartData, disables the SAVE SKY btn, & purges the LS_KEY entry that would otherwise rehydrate on next overlay open / page refresh. Mirrors the user's spec ("shouldn't be targeting the user model anyway, only the character/seat model" — and there's currently no character/seat draft in the PICK SKY flow).
Both handlers defer the window.showGuard readiness check to click-time rather than gating the listener bind itself: window.showGuard is assigned by a base.html script that lives BELOW the content block, so an `if (window.showGuard)` gate at script-execute time would skip the bind entirely (we hit this writing the applet handler — manifested as portal class never receiving 'active' on click).
SCSS: extends the existing #id_sky_delete_form absolute-center rule onto the two new btn IDs (#id_sky_delete_btn, #id_applet_sky_delete_btn). #id_applet_my_sky picks up position:relative as the absolute anchor for the applet btn.
FTs: MySkyAppletDelTest (applet → DEL → guard → OK → reload, asserts User cleared + LS purged + form re-renders) & PickSkyDelTest (overlay → fill form → wheel paints → DEL → guard → OK, asserts SVG empty + form blank + LS purged). Both red before the wiring, green after; full sky suite (46 tests) green.
Code architected by Disco DeDisco <discodedisco@outlook.com>
Git commit message Co-Authored-By:
Claude Sonnet 4.6 <noreply@anthropic.com>
DEL btn (.btn-danger, "Forget sky?" data-confirm wired to the global #id_guard_portal) sits absolutely centered inside .sky-wheel-col; OK submits a POST to the new sky_delete view, which clears every sky_* field on the User model & redirects back to /dashboard/sky/.
The sky.html aperture is now uniform across saved/unsaved: form-col is always flex-column align-center justify-center so the fields + SAVE SKY pair sits visually centered. body.sky-saved adds *only* the snap-binary scroll layer (scroll-snap-type:y, modal-body display:contents, cols min-height:100% scroll-snap-align:start, wheel-col aspect-ratio cap released, form-col flex:0 0 auto so the snap basis wins) — the column-stacking is no longer gated.
Async save: SAVE SKY's success branch now calls _activateSavedState(), which adds body.sky-saved, draws the wheel from _lastChartData, pins overlay.scrollTop to the form section's offsetTop, then runs the existing _scrollApertureToTop ease-out so the wheel reveals from above instead of replacing the form with a hard cut. The wheel preview that previously redrew during typing is now gated on _savedSky — pre-first-save typing fetches the chart data (so SAVE SKY enables) but does not render the wheel, mirroring the My Sky applet's "no wheel until saved" UX. The in-room PICK SKY overlay (_sky_overlay.html) still previews live, deliberately untouched.
Pre-save the wheel-col is hidden via `body:not(.sky-saved) .sky-page .sky-wheel-col { display: none }`, so the empty SVG can't shunt the form below the fold (& the DEL btn rides the same selector since it lives inside .sky-wheel-col).
Tests: SkyDeleteTest IT class (5: clears fields, redirects, 405 on GET, login required, preserves unrelated user fields). MySkyDeleteFlowTest FT class (3: DEL btn visibility gated on sky data, NVM dismisses w. data intact, OK clears + reverts body class). MySkyAsyncSaveTest FT (1: fresh user → SAVE SKY → body picks up sky-saved, wheel SVG populates, DEL btn becomes visible — all without a page reload). All 13 sky FTs + sky ITs green; existing MySkyApertureSnapScrollTest & MySkyTimezoneRefreshTest still pass.
Code architected by Disco DeDisco <discodedisco@outlook.com>
Git commit message Co-Authored-By:
Claude Sonnet 4.6 <noreply@anthropic.com>
post-save the .sky-page aperture flips into scroll-snap-y-mandatory mode: wheel-col & form-col each fill the aperture & carry scroll-snap-align:start, so vertical scroll toggles between them rather than free-flowing through both. Modal-body uses display:contents so the cols become direct flex children of .sky-page (where min-height:100% resolves against the explicit aperture height); wheel-col's aspect-ratio/max-height caps are released under body.sky-saved so the section actually fills the aperture instead of clipping at 480px. SAVE SKY's success branch calls _scrollApertureToTop(), a 280ms RAF loop w. ease-out cubic so the user lands back on the wheel after confirming from the form section. New FT class MySkyApertureSnapScrollTest covers (T1) snap-type:y mandatory + scroll-snap-align:start on both cols, (T2) scrollTop returns to 0 after SAVE SKY click; both red before the SCSS+JS, green after. Snap behavior is gated on body.sky-saved (set by sky_view based on user.sky_chart_data) so the pre-save form-only flow is untouched.
Code architected by Disco DeDisco <discodedisco@outlook.com>
Git commit message Co-Authored-By:
Claude Sonnet 4.6 <noreply@anthropic.com>
selectPlace + geolocation now zero out tzInput.value & tzHint before schedulePreview, so the existing `if (!tzInput.value && data.timezone)` backfill in schedulePreview's success path actually fires when the user changes location after a saved sky exists. Without the clear, the saved/previous TZ stayed pinned & the chart was recomputed against the wrong timezone. Fix mirrored across sky.html (Dashsky), _applet-my-sky.html (My Sky applet entry form), and _sky_overlay.html (PICK SKY in-room overlay). New FT MySkyTimezoneRefreshTest.test_changing_place_refreshes_auto_detected_timezone seeds a user w. saved Baltimore/America/New_York, mocks Nominatim + sky/preview to return Camarillo + America/Los_Angeles, picks the suggestion, and asserts the TZ field updates.
Code architected by Disco DeDisco <discodedisco@outlook.com>
Git commit message Co-Authored-By:
Claude Sonnet 4.6 <noreply@anthropic.com>
- _scripts.html: add note.js alongside dashboard.js — Note global now available on dashboard
page, enabling Note.handleSaveResponse from _applet-my-sky.html save handler
- test_dashboard.py: remove stale new-note applet seed (renamed to new-post/billboard)
- test_room_tray.py: remove stale new-note applet seed
Code architected by Disco DeDisco <discodedisco@outlook.com>
Git commit message Co-Authored-By:
Claude Sonnet 4.6 <noreply@anthropic.com>
sky_save now re-fetches from PySwiss server-side on save so stored
chart_data always carries enriched element format (contributors/stellia/
parades). New sky/data endpoint serves fresh PySwiss data to the My Sky
applet on load, replacing the stale inline json_script approach.
natus-wheel.js: sign ring slices (data-sign-name) and house ring slices
(data-house) now have click handlers with _activateSign/_activateHouse;
em-dash fallback added for classic elements with empty contributor lists.
Action URLs sky/preview, sky/save, sky/data lose trailing slashes.
Jasmine: T12 sign tooltip, T13 house tooltip, T14 enriched element
contributor display (symbols, Stellium/Parade formations, em-dash fallback).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- _computeConjunctions(planets, threshold=8) detects conjunct pairs
- Tick lines (nw-planet-tick) radiate from each planet circle outward
past the zodiac ring; animated via attrTween; styled with --pri* colours
- planetEl.raise() on mouseover puts hovered planet on top in SVG z-order
- Dual tooltip: hovering a conjunct planet shows #id_natus_tooltip_2 beside
the primary, populated with the hidden partner's sign/degree/retrograde data
- #id_natus_tooltip_2 added to home.html, sky.html, room.html
- _natus.scss: tick line rules + both tooltip IDs share all selectors;
#id_natus_confirm gets position:relative/z-index:1 to fix click intercept
- NatusWheelSpec.js: T7 (tick extends past zodiac), T8 (raise to front),
T9j (conjunction dual tooltip) in new conjunction describe block
- FT T3 trimmed to element-ring hover only; planet/conjunction hover
delegated to Jasmine (ActionChains planet-circle hover unreliable in Firefox)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Move #id_natus_tooltip out of #id_applets_container (container-type:
inline-size breaks position:fixed) → add to home.html alongside
#id_tooltip_portal
- Move #id_natus_tooltip out of .natus-modal-wrap (transform breaks
position:fixed) → place as sibling of .natus-overlay in room.html
- Add _positionTooltip() helper in natus-wheel.js: flips tooltip to
left of cursor when it would overflow right edge; clamps both axes
- Replace hardcoded 280px in dashboard.js palette tooltip with measured
offsetWidth; add left-edge floor (Math.max margin)
- Planet tooltip format: @14.0° Capricorn (<svg-icon>) using preloaded
_signPaths; falls back to unicode symbol if not yet loaded
- Add .tt-sign-icon SCSS: fill:currentColor, vertical-align:middle
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Clicking a swatch instantly swaps the body palette class for a live
preview; OK commits silently (POST, no reload); click-elsewhere or
10 s auto-dismiss reverts. Tooltip portal shows label, shoptalk,
lock state. Locked swatches show × (disabled). 20 FTs green.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- sky_view passes page_class="page-sky" so the footer pins correctly
- _natus.scss: page-sky aperture block (mirrors page-wallet pattern);
sky-page stacks wheel above form via flex order + page-level scroll;
wheel col uses aspect-ratio:1/1 so it takes natural square size without
compressing to fit the form
- natus-wheel.js: _layout() sets viewBox + preserveAspectRatio="xMidYMid meet"
so the wheel is always centred inside the SVG element regardless of its
aspect ratio (fixes left-alignment in the dashboard applet)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- User model: sky_birth_dt/lat/lon/place/house_system/chart_data fields (lyric migration 0018)
- Applet seed: my-sky (6×6, dashboard context) via applets migration 0009
- dashboard/sky/ — full monoapplet page: natus form + D3 wheel, LS key scoped to dashboard:sky; saves to User model
- dashboard/sky/preview/ — PySwiss proxy (same logic as epic:natus_preview, no seat check)
- dashboard/sky/save/ — persists 6 sky fields to User via update_fields
- _applet-my-sky.html: tile with h2 link to /dashboard/sky/
- 2 FTs (applet→link→form, localStorage persistence) + 12 ITs — all green
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
game kit: new Applet model rows (context=game-kit) for Trinkets, Tokens, Card Decks, Dice Sets via applets migration 0008; _game_kit_context() helper in gameboard.views; toggle_game_kit_sections view + URL; new _game_kit_sections.html (HTMX-swappable, visibility-conditional) + _game_kit_applet_menu.html partials; game_kit.html wired to gear btn + menu; Dice Sets now renders _forthcoming.html partial; 16 new green ITs in GameKitViewTest + ToggleGameKitSectionsViewTest
login form: .input-group now position:fixed + vertically centred (top:50%) across all breakpoints as default; landscape block reduced to left/right sidebar offsets only; form-control width 24rem, text-align:center; alert block moved below h2 in base.html; alert margin 0.75rem all sides; home.html header switches between Howdy Stranger (anon) and Dashboard (authed)
room.html position indicators: slots 3/4/5 (AC/SC/EC) column order flipped via SCSS data-slot selectors so .fa-chair sits table-side and label+status icon sit outward
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>