Compare commits

...

5 Commits

Author SHA1 Message Date
Disco DeDisco
f348a19312 my-sea portrait SPREAD dropdown opens UP, not down — top: 100% was extending the list below the form col, which on portrait sits flush at the bottom of the visible aperture w. navbar/footer pinned beneath it (options unreachable). bottom: 100% (+ margin flipped to bottom) grows the list into the abundant green aperture above. Chained &.sea-form-col per [[feedback-scss-import-order-specificity]] to beat card-deck's later-loaded base
Some checks failed
ci/woodpecker/push/pyswiss Pipeline was successful
ci/woodpecker/push/main Pipeline failed
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 12:04:51 -04:00
Disco DeDisco
bc4565f161 my-sea portrait form-col grid fix: chain .sea-form-col.my-sea-form-col so the 2-class selector beats _card-deck.scss's base .sea-form-col { display: flex } regardless of source order (card-deck loads AFTER gameboard in core.scss, so the prior 1-class selector lost to source order and the grid never took effect)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 12:02:07 -04:00
Disco DeDisco
4963237420 my-sea portrait form-col split: SPREAD field + action btns LEFT, DECKS RIGHT — fits the form on a phone-portrait viewport without DECKS pushing the AUTO DRAW / DEL row off-screen. CSS grid w. display: contents on .sea-form-main flattens the intermediate wrapper so its children participate directly in the grid
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 11:58:41 -04:00
Disco DeDisco
191dad5365 my-sea hex-btn state-machine FT pin: extend test_landing_renders_hex_with_free_draw_btn to assert PAID DRAW + GATE VIEW are absent for fresh users — closes the mutual-exclusion gap (the other two states already pin the same invariant from their own directions; this adds the FREE-DRAW side). Docstring spells out the 3-way state machine for future readers
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 11:56:09 -04:00
Disco DeDisco
611ca9b5b4 my-sea polish v2: portrait .my-sea-picker stacks form col BELOW the cross (mirrors gameroom SEA SELECT modal) + sync MySeaGatekeeperPageTest.test_paid_draw_commits_token_and_redirects_to_picker w. iter-6c row-delete + ?phase=picker semantics (was pinning iter-6a behavior; pipeline #319 caught it)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 11:52:17 -04:00
2 changed files with 106 additions and 11 deletions

View File

@@ -205,8 +205,20 @@ class MySeaDrawSeaLandingTest(FunctionalTest):
"""User w. sig → /gameboard/my-sea/ shows the DRY table hex (re- """User w. sig → /gameboard/my-sea/ shows the DRY table hex (re-
used from my-sign / the room shell) w. a central FREE DRAW btn. used from my-sign / the room shell) w. a central FREE DRAW btn.
Element ID `id_draw_sea_btn` describes intent (the draw entry Element ID `id_draw_sea_btn` describes intent (the draw entry
point) — a future sprint will conditionally swap the label to point); the hex-center btn is a 3-way state machine:
DRAW SEA once the daily free has been used.""" - FREE DRAW (`#id_draw_sea_btn`) — fresh user (no active row, no
deposit). Pinned here.
- PAID DRAW (`#id_my_sea_paid_draw_btn`) — deposit reserved.
Pinned by `MySeaLandingPaidDrawTest`.
- GATE VIEW (`#id_my_sea_gate_view_btn`) — quota spent, no
deposit (post-DEL). Pinned by
`MySeaDeleteDrawAndGuardPortalTest` (the DEL-confirm flow).
The three IDs are mutually exclusive — only one renders at a
time. This test pins the fresh-user side of that exclusion
(FREE DRAW present, the other two absent); the deposit + post-
DEL sides have their own dedicated FTs that pin the same
invariant from the other directions."""
self.create_pre_authenticated_session(self.email) self.create_pre_authenticated_session(self.email)
self.browser.get(self.live_server_url + "/gameboard/my-sea/") self.browser.get(self.live_server_url + "/gameboard/my-sea/")
# data-phase=landing on the page wrapper # data-phase=landing on the page wrapper
@@ -221,6 +233,17 @@ class MySeaDrawSeaLandingTest(FunctionalTest):
self.assertIn("FREE", btn.text.upper()) self.assertIn("FREE", btn.text.upper())
self.assertIn("DRAW", btn.text.upper()) self.assertIn("DRAW", btn.text.upper())
self.assertIn("btn-primary", btn.get_attribute("class")) self.assertIn("btn-primary", btn.get_attribute("class"))
# Mutual exclusion: PAID DRAW + GATE VIEW must NOT also render.
self.assertEqual(
len(page.find_elements(By.CSS_SELECTOR, "#id_my_sea_paid_draw_btn")),
0,
"PAID DRAW btn must not render when no deposit is reserved",
)
self.assertEqual(
len(page.find_elements(By.CSS_SELECTOR, "#id_my_sea_gate_view_btn")),
0,
"GATE VIEW btn must not render when no active draw row exists",
)
# ── Test 2 ─────────────────────────────────────────────────────────────── # ── Test 2 ───────────────────────────────────────────────────────────────
@@ -1337,9 +1360,13 @@ class MySeaGatekeeperPageTest(FunctionalTest):
def test_paid_draw_commits_token_and_redirects_to_picker(self): def test_paid_draw_commits_token_and_redirects_to_picker(self):
"""PAID DRAW commits the deposited token (FREE token gets """PAID DRAW commits the deposited token (FREE token gets
consumed → user's token count drops by 1); server resets the consumed → user's token count drops by 1); iter-6c spec then
MySeaDraw row (hand=[], created_at=now, deposit cleared); user DROPS the active_draw row entirely + redirects to /gameboard/
lands back on /gameboard/my-sea/ ready to draw a fresh hand.""" my-sea/?phase=picker so the user lands directly in the picker
ready to draw. Previously the row was preserved w. reset
created_at — that looped the user back to GATE VIEW
(`quota_spent=True` w. row present). See
[[sprint-my-sea-iter-6c-may20]] for the rationale."""
from apps.gameboard.models import MySeaDraw from apps.gameboard.models import MySeaDraw
from apps.lyric.models import Token from apps.lyric.models import Token
self._save_empty_hand_draw() self._save_empty_hand_draw()
@@ -1360,19 +1387,23 @@ class MySeaGatekeeperPageTest(FunctionalTest):
) )
) )
paid_draw.click() paid_draw.click()
# Redirect lands on /gameboard/my-sea/ (landing or picker). # Redirect lands on /gameboard/my-sea/?phase=picker so the user
# drops straight into the picker (no FREE-DRAW landing click).
self.wait_for( self.wait_for(
lambda: self.assertIn("/gameboard/my-sea/", self.browser.current_url) lambda: self.assertIn(
"/gameboard/my-sea/?phase=picker", self.browser.current_url
)
) )
# FREE token consumed. # FREE token consumed.
self.assertEqual( self.assertEqual(
Token.objects.filter(user=self.gamer, token_type=Token.FREE).count(), Token.objects.filter(user=self.gamer, token_type=Token.FREE).count(),
free_count_before - 1, free_count_before - 1,
) )
# MySeaDraw row: hand reset to empty, deposit cleared, fresh quota. # Row dropped — `active_draw is None` is the signal that lets
draw = MySeaDraw.objects.get(user=self.gamer) # the my_sea view honour the `?phase=picker` override.
self.assertEqual(draw.hand, []) self.assertFalse(
self.assertIsNone(draw.deposit_token_id) MySeaDraw.objects.filter(user=self.gamer).exists()
)
class MySeaLandingPaidDrawTest(FunctionalTest): class MySeaLandingPaidDrawTest(FunctionalTest):

View File

@@ -305,6 +305,15 @@ body.page-gameboard {
align-items: center; align-items: center;
justify-content: center; justify-content: center;
gap: 1rem; gap: 1rem;
// Portrait — stack the cross spread above the form col (mirrors the
// gameroom SEA SELECT modal's `@media (max-width: 600px)` stack
// pattern in `_card-deck.scss`). Landscape keeps the side-by-side
// layout since horizontal real-estate is the abundant axis there.
// User-spec 2026-05-20.
@media (orientation: portrait) {
flex-direction: column;
}
} }
// .my-sea-cross renders all 6 surrounding positions (crown/leave/lay/ // .my-sea-cross renders all 6 surrounding positions (crown/leave/lay/
@@ -492,6 +501,45 @@ body.page-gameboard {
overflow: visible; overflow: visible;
} }
// Portrait — split the form into two columns. LEFT carries the
// SPREAD field (label + reversal hint + combobox) above the action
// btns (AUTO DRAW / GATE VIEW + DEL); RIGHT carries the DECKS
// section (label + GRAVITY/LEVITY stacks) spanning both rows.
// Without this rearrange, on a phone-portrait viewport the stacked
// form col runs off the bottom of the viewport — DECKS lives above
// the action btns + everything below the fold (user-spec
// 2026-05-21). `.sea-form-main` uses `display: contents` so its
// `.sea-field` + `.sea-stacks` children act as direct grid items of
// the form col despite the intermediate wrapper in the DOM.
//
// Selector chains `.sea-form-col.my-sea-form-col` to win against
// `_card-deck.scss`'s base `.sea-form-col { display: flex; flex-
// direction: column }` (same 1-class specificity; card-deck loads
// AFTER gameboard in `core.scss`, so source order would otherwise
// overrule my-sea's grid). Chained 2-class selector pulls
// specificity ahead regardless of source order.
@media (orientation: portrait) {
&.sea-form-col {
flex: 0 0 auto;
max-width: none;
width: 100%;
display: grid;
grid-template-columns: 1fr 1fr;
grid-template-areas:
"field stacks"
"actions stacks";
column-gap: 1rem;
row-gap: 0.5rem;
align-items: start;
.sea-form-main { display: contents; }
.sea-field { grid-area: field; margin-bottom: 0; }
.sea-stacks { grid-area: stacks; margin: 0; justify-content: center; }
.sea-form-actions { grid-area: actions; align-self: end; padding-top: 0; }
}
}
// Bump the dropdown z-index well above the picker's stacking ints // Bump the dropdown z-index well above the picker's stacking ints
// (cover z:3, cross z:4, modal stage z:9999 only opens on draw // (cover z:3, cross z:4, modal stage z:9999 only opens on draw
// anyway). 1000 sits above any in-page layer the user might be // anyway). 1000 sits above any in-page layer the user might be
@@ -499,6 +547,22 @@ body.page-gameboard {
.sea-select-list { .sea-select-list {
z-index: 1000; z-index: 1000;
} }
// Portrait — open the SPREAD dropdown UPWARD instead of downward.
// The portrait form col sits at the bottom of the viewport (below
// the cross spread) w. the navbar/footer pinned beneath it, so the
// default `top: 100%` dropdown extends BELOW the visible aperture
// + the user can't scroll to it. Flipping to `bottom: 100%` makes
// the list grow upward into the abundant green aperture above.
// Chained `&.sea-form-col` to beat card-deck's later-loaded base
// (per [[feedback-scss-import-order-specificity]]).
@media (orientation: portrait) {
&.sea-form-col .sea-select .sea-select-list {
top: auto;
bottom: 100%;
margin: 0 0 0.2rem;
}
}
} }
// LOCK HAND post-commit visual-lock: dim everything that mutates the // LOCK HAND post-commit visual-lock: dim everything that mutates the