From 3faf45f71eb6c6a5260731f6a4b8a81b24edbb7c Mon Sep 17 00:00:00 2001 From: hided62 Date: Wed, 12 Aug 2026 15:43:41 +0000 Subject: [PATCH] fix: align main command editor flow with ref --- app/game-api/src/turns/reservedTurns.ts | 9 +- app/game-api/test/router.test.ts | 3 +- app/game-frontend/e2e/mainNavigation.spec.ts | 85 +++++++- .../command/ReservedCommandEditor.vue | 197 ++++++++++-------- .../src/components/command/types.ts | 1 + .../src/components/main/CommandListPanel.vue | 2 + app/game-frontend/src/stores/mainDashboard.ts | 19 ++ app/game-frontend/src/views/MainView.vue | 24 ++- 8 files changed, 237 insertions(+), 103 deletions(-) diff --git a/app/game-api/src/turns/reservedTurns.ts b/app/game-api/src/turns/reservedTurns.ts index ffb44394..4487743e 100644 --- a/app/game-api/src/turns/reservedTurns.ts +++ b/app/game-api/src/turns/reservedTurns.ts @@ -19,6 +19,7 @@ export interface ReservedTurnView { export interface ReservedTurnSnapshot { revision: number; turns: ReservedTurnView[]; + autorunLimit?: number | null; } export interface ReservedTurnUpdate { @@ -170,13 +171,19 @@ export const listGeneralTurns = async (db: DatabaseClient, generalId: number): P }; export const getGeneralTurnSnapshot = async (db: DatabaseClient, generalId: number): Promise => { - const [turns, revisionRow] = await Promise.all([ + const [turns, revisionRow, general] = await Promise.all([ loadGeneralTurns(db, generalId), db.generalTurnRevision.findUnique({ where: { generalId } }), + db.general.findUnique({ where: { id: generalId }, select: { meta: true } }), ]); + const rawAutorunLimit = isRecord(general?.meta) ? general.meta.autorun_limit : undefined; return { revision: revisionRow?.revision ?? 0, turns: serializeTurnList(turns), + autorunLimit: + typeof rawAutorunLimit === 'number' && Number.isFinite(rawAutorunLimit) + ? Math.trunc(rawAutorunLimit) + : null, }; }; diff --git a/app/game-api/test/router.test.ts b/app/game-api/test/router.test.ts index e0b80bca..32dd49be 100644 --- a/app/game-api/test/router.test.ts +++ b/app/game-api/test/router.test.ts @@ -824,7 +824,7 @@ describe('appRouter', () => { }); it('returns reserved general turns', async () => { - const general = buildGeneralRow({ id: 11 }); + const general = buildGeneralRow({ id: 11, meta: { autorun_limit: 2408 } }); const generalTurns: GeneralTurnRow[] = [ { id: 1, @@ -839,6 +839,7 @@ describe('appRouter', () => { const response = await caller.turns.reserved.getGeneral({ generalId: 11 }); expect(response.revision).toBe(0); + expect(response.autorunLimit).toBe(2408); expect(response.turns[0]?.action).toBe('che_화계'); expect(response.turns[0]?.index).toBe(0); }); diff --git a/app/game-frontend/e2e/mainNavigation.spec.ts b/app/game-frontend/e2e/mainNavigation.spec.ts index fb10f760..4ed3a60b 100644 --- a/app/game-frontend/e2e/mainNavigation.spec.ts +++ b/app/game-frontend/e2e/mainNavigation.spec.ts @@ -32,6 +32,7 @@ type NavigationFixture = { refreshDelayMs?: number; largeCommandTable?: boolean; reservedTurns?: Array<{ index: number; action: string; args: Record }>; + autorunLimit?: number | null; dashboardResponses?: Array<{ bytes: number; contextKind: string | null; @@ -403,7 +404,11 @@ const installFixture = async (page: Page, state: NavigationFixture) => { } if (operation === 'turns.getCommandTable') return response({ general: [], nation: [] }); if (operation === 'turns.reserved.getGeneral' || operation === 'turns.reserved.getNation') { - return response({ turns: state.reservedTurns ?? [], revision: 0 }); + return response({ + turns: state.reservedTurns ?? [], + revision: 0, + autorunLimit: state.autorunLimit ?? null, + }); } if (operation === 'messages.getRecent') return response(emptyMessages(state.permission)); if (operation === 'messages.getContacts') return response({ nation: [] }); @@ -641,11 +646,12 @@ test('main cards and command input stay inside their Ref-sized grid slots', asyn generalMeCalls: 0, operations: [], largeCommandTable: true, - reservedTurns: Array.from({ length: 14 }, (_, index) => ({ + reservedTurns: Array.from({ length: 30 }, (_, index) => ({ index, - action: `command-${index}`, + action: index === 0 ? '휴식' : `command-${index}`, args: {}, })), + autorunLimit: 2224, }; await installFixture(page, state); await page.setViewportSize({ width: 1200, height: 900 }); @@ -728,6 +734,27 @@ test('main cards and command input stay inside their Ref-sized grid slots', asyn expect(new Set(desktopGeometry.visibleControlBoxes.map(({ y }) => y)).size).toBe(1); expect(desktopGeometry.bottomActionBoxes).toHaveLength(3); expect(new Set(desktopGeometry.bottomActionBoxes.map(({ y }) => y)).size).toBe(1); + await expect(page.locator('[data-main-target="commands"] .edit-column button')).toHaveCount(15); + const autonomousRest = page.locator('[data-main-target="commands"] .action-column > div').first(); + await expect(autonomousRest).toContainText('휴식(자율 행동)'); + expect(await autonomousRest.evaluate((element) => getComputedStyle(element).color)).toBe('rgb(170, 255, 255)'); + + const tenthTurnButton = page.getByRole('button', { name: '10턴 명령 입력' }); + await tenthTurnButton.click(); + const quickPicker = page.getByTestId('command-picker'); + await expect(quickPicker).toBeVisible(); + const quickPickerAlignment = await quickPicker.evaluate((element) => { + const row = element + .closest('.reserved-command-editor') + ?.querySelector('.action-column > div:nth-child(10)'); + if (!row) throw new Error('10th command row missing'); + return { + pickerTop: element.getBoundingClientRect().top, + rowTop: row.getBoundingClientRect().top, + }; + }); + expect(quickPickerAlignment.pickerTop - quickPickerAlignment.rowTop).toBeCloseTo(30, 0); + await quickPicker.getByRole('button', { name: '명령 입력 닫기' }).click(); const modeButton = page.locator('[data-main-target="commands"] .control-pad').getByRole('button', { name: '고급 모드', @@ -736,6 +763,27 @@ test('main cards and command input stay inside their Ref-sized grid slots', asyn await modeButton.focus(); await expect(modeButton).toBeFocused(); await modeButton.click(); + const advancedControlGeometry = await page.locator('[data-main-target="commands"] .reserved-command-editor').evaluate( + (editor) => { + const range = editor.querySelector('.range-menu'); + const recent = [...editor.querySelectorAll('.control-pad summary')].find((element) => + element.textContent?.includes('최근 실행') + ); + const advanced = editor.querySelector('.advanced-actions'); + const queue = editor.querySelector('.queue-grid'); + if (!range || !recent || !advanced || !queue) throw new Error('advanced command controls missing'); + return { + rangeTop: range.getBoundingClientRect().top, + recentTop: recent.getBoundingClientRect().top, + advancedTop: advanced.getBoundingClientRect().top, + advancedBottom: advanced.getBoundingClientRect().bottom, + queueTop: queue.getBoundingClientRect().top, + }; + } + ); + expect(advancedControlGeometry.rangeTop).toBe(advancedControlGeometry.recentTop); + expect(advancedControlGeometry.advancedTop).toBeGreaterThan(advancedControlGeometry.rangeTop); + expect(advancedControlGeometry.advancedBottom).toBeLessThanOrEqual(advancedControlGeometry.queueTop); await page.locator('[data-main-target="commands"] .select-command').click(); const picker = page.getByTestId('command-picker'); await expect(picker).toBeVisible(); @@ -765,6 +813,32 @@ test('main cards and command input stay inside their Ref-sized grid slots', asyn expect(pickerGeometry.horizontalOverflow).toBeLessThanOrEqual(0); await picker.getByRole('button', { name: '명령 입력 닫기' }).click(); + await page.locator('[data-main-target="commands"] .control-pad').getByRole('button', { name: '일반 모드' }).click(); + const collapsedPanelHeight = await page + .locator('[data-main-target="commands"]') + .evaluate((element) => element.getBoundingClientRect().height); + await page.locator('[data-main-target="commands"] .bottom-actions').getByRole('button', { name: '펼치기' }).click(); + await expect(page.locator('[data-main-target="commands"] .edit-column button')).toHaveCount(30); + const expandedDesktopGeometry = await page.locator('.layout-desktop').evaluate((layout) => { + const commands = layout.querySelector('[data-main-target="commands"]'); + const city = layout.querySelector('[data-main-target="city"]'); + const nation = layout.querySelector('[data-main-target="nation"]'); + if (!commands || !city || !nation) throw new Error('expanded desktop panels missing'); + return { + commandHeight: commands.getBoundingClientRect().height, + commandBottom: commands.getBoundingClientRect().bottom, + cityBottom: city.getBoundingClientRect().bottom, + nationTop: nation.getBoundingClientRect().top, + verticalOverflow: commands.scrollHeight - commands.clientHeight, + overflowY: getComputedStyle(commands).overflowY, + }; + }); + expect(expandedDesktopGeometry.commandHeight).toBeGreaterThan(collapsedPanelHeight); + expect(expandedDesktopGeometry.verticalOverflow).toBeLessThanOrEqual(0); + expect(expandedDesktopGeometry.overflowY).toBe('visible'); + expect(expandedDesktopGeometry.cityBottom).toBe(expandedDesktopGeometry.commandBottom); + expect(expandedDesktopGeometry.nationTop).toBeGreaterThanOrEqual(expandedDesktopGeometry.commandBottom); + const captureProgress = async (name: string) => { if (!artifactRoot) return; await mkdir(artifactRoot, { recursive: true }); @@ -852,9 +926,9 @@ test('main cards and command input stay inside their Ref-sized grid slots', asyn }; await captureProgress('desktop-1200'); - await page.locator('[data-main-target="commands"] .control-pad').getByRole('button', { name: '일반 모드' }).click(); await page.setViewportSize({ width: 500, height: 900 }); await expect(page.locator('.layout-mobile')).toBeVisible(); + await page.locator('[data-main-target="commands"] .bottom-actions').getByRole('button', { name: '펼치기' }).click(); expect(await page.evaluate(() => document.documentElement.scrollWidth)).toBeLessThanOrEqual(500); await expect(page.locator('[data-main-target="city"] [role="progressbar"]')).toHaveCount(8); await expect(page.locator('[data-main-target="general"] [role="progressbar"]')).toHaveCount(4); @@ -889,7 +963,7 @@ test('main cards and command input stay inside their Ref-sized grid slots', asyn controlBoxes, }; }); - expect(mobileGeometry.panelHeight).toBe(645); + expect(mobileGeometry.panelHeight).toBeGreaterThan(645); expect(mobileGeometry.editorLeft).toBeGreaterThanOrEqual(mobileGeometry.panelLeft); expect(mobileGeometry.editorRight).toBeLessThanOrEqual(mobileGeometry.panelRight); expect(mobileGeometry.horizontalOverflow).toBeLessThanOrEqual(0); @@ -897,6 +971,7 @@ test('main cards and command input stay inside their Ref-sized grid slots', asyn expect(mobileGeometry.controlColumns.split(' ')).toHaveLength(3); expect(mobileGeometry.controlBoxes).toHaveLength(3); expect(new Set(mobileGeometry.controlBoxes.map(({ y }) => y)).size).toBe(1); + await expect(page.locator('[data-main-target="commands"] .edit-column button')).toHaveCount(30); await captureProgress('mobile-500'); }); diff --git a/app/game-frontend/src/components/command/ReservedCommandEditor.vue b/app/game-frontend/src/components/command/ReservedCommandEditor.vue index 54db3422..44f462de 100644 --- a/app/game-frontend/src/components/command/ReservedCommandEditor.vue +++ b/app/game-frontend/src/components/command/ReservedCommandEditor.vue @@ -50,6 +50,7 @@ const commandArgsValid = ref(false); const expanded = ref(false); const menuRevision = ref(0); const pendingReservation = ref(null); +const collapsedRowCount = 15; const loadStorage = (key: string) => { storage.value = new CommandStorage(key); @@ -108,7 +109,11 @@ const labelMap = computed(() => { for (const group of groups) for (const command of group.values) map.set(command.key, command.name); return map; }); -const displayRows = computed(() => props.rows.slice(0, expanded.value || props.compact ? props.rows.length : 14)); +const displayRows = computed(() => + props.rows.slice(0, expanded.value || props.compact ? props.rows.length : collapsedRowCount) +); +const quickPickerTop = computed(() => `${70 + (quickTarget.value ?? 0) * 34.4}px`); +const rowLabel = (row: ReservedCommandRow): string => row.label ?? labelMap.value.get(row.action) ?? row.action; const selectedIndices = () => normalizedSelection(selected.value, previousSelected.value, props.rows.length); const pattern = () => extractPattern(props.rows, selectedIndices()); const touchMenus = () => (menuRevision.value += 1); @@ -396,6 +401,87 @@ const clickOutsideMenu = (event: Event) => { +
+
+ 선택한 턴을 + +
+ +
+
{
- {{ row.label ?? labelMap.get(row.action) ?? row.action }} + {{ rowLabel(row) }} + (자율 행동)
@@ -461,87 +549,6 @@ const clickOutsideMenu = (event: Event) => {
-
-
- 선택한 턴을 - -
- -
-
@@ -550,7 +557,12 @@ const clickOutsideMenu = (event: Event) => {
-
+
{{ quickTarget === null ? '선택한 턴' : `${quickTarget + 1}턴` }} 명령 입력 @@ -628,6 +640,9 @@ const clickOutsideMenu = (event: Event) => { gap: 4px; padding: 3px 0; } +.queue-area { + order: 2; +} .control-pad > button, .clock, .legacy-menu > summary, @@ -779,6 +794,13 @@ const clickOutsideMenu = (event: Event) => { .action-column > div:nth-child(even) { background: #071638; } +.action-column > div.autonomous { + color: #aaffff; +} +.action-column small { + font-size: 0.72em; + line-height: 1; +} .edit-column button { background: #444; cursor: pointer; @@ -786,6 +808,7 @@ const clickOutsideMenu = (event: Event) => { .advanced-actions { display: grid; grid-template-columns: 5fr 7fr; + order: 1; } .advanced-actions > * { border-radius: 0 !important; @@ -808,6 +831,10 @@ const clickOutsideMenu = (event: Event) => { background: #303030; box-shadow: 0 6px 16px #000; } +.reserved-command-editor:not(.compact) .command-picker { + max-height: none; + overflow: visible; +} .command-picker > header { display: flex; align-items: center; diff --git a/app/game-frontend/src/components/command/types.ts b/app/game-frontend/src/components/command/types.ts index 58bf0e13..d01f78d4 100644 --- a/app/game-frontend/src/components/command/types.ts +++ b/app/game-frontend/src/components/command/types.ts @@ -49,6 +49,7 @@ export type ReservedCommandRow = { time?: string; year?: number; month?: number; + autonomous?: boolean; }; export type CommandPatternEntry = { diff --git a/app/game-frontend/src/components/main/CommandListPanel.vue b/app/game-frontend/src/components/main/CommandListPanel.vue index 72ac9811..d44b72fe 100644 --- a/app/game-frontend/src/components/main/CommandListPanel.vue +++ b/app/game-frontend/src/components/main/CommandListPanel.vue @@ -12,6 +12,7 @@ const props = defineProps<{ currentYear?: number; currentMonth?: number; turnTermMinutes?: number; + autorunLimit?: number | null; storageKey?: string; }>(); @@ -43,6 +44,7 @@ const rows = computed(() => { label: labelMap.value.get(turn.action) ?? turn.action, year: Math.floor(absoluteMonth / 12), month: (absoluteMonth % 12) + 1, + autonomous: props.autorunLimit != null && absoluteMonth <= props.autorunLimit - 1, time: date ? term >= 5 ? `${String(date.getUTCHours()).padStart(2, '0')}:${String(date.getUTCMinutes()).padStart(2, '0')}` diff --git a/app/game-frontend/src/stores/mainDashboard.ts b/app/game-frontend/src/stores/mainDashboard.ts index c178bd5c..4fd05e72 100644 --- a/app/game-frontend/src/stores/mainDashboard.ts +++ b/app/game-frontend/src/stores/mainDashboard.ts @@ -65,6 +65,7 @@ export const useMainDashboardStore = defineStore('mainDashboard', () => { boardAccess?: BoardAccess | null; reservedGeneralTurns?: ReservedTurnView[] | null; reservedGeneralRevision?: number; + reservedGeneralAutorunLimit?: number | null; globalRecords?: RecentRecord[]; generalRecords?: RecentRecord[]; worldHistory?: RecentRecord[]; @@ -94,6 +95,7 @@ export const useMainDashboardStore = defineStore('mainDashboard', () => { const boardAccess = ref(null); const reservedGeneralTurns = ref(null); const reservedGeneralRevision = ref(0); + const reservedGeneralAutorunLimit = ref(null); const globalRecords = ref([]); const generalRecords = ref([]); const worldHistory = ref([]); @@ -331,6 +333,7 @@ export const useMainDashboardStore = defineStore('mainDashboard', () => { boardAccess.value = null; reservedGeneralTurns.value = null; reservedGeneralRevision.value = 0; + reservedGeneralAutorunLimit.value = null; resetRecentRecords(null); commandTableRevision = null; boardAccessRevision = null; @@ -351,6 +354,7 @@ export const useMainDashboardStore = defineStore('mainDashboard', () => { boardAccess.value = null; reservedGeneralTurns.value = null; reservedGeneralRevision.value = 0; + reservedGeneralAutorunLimit.value = null; resetRecentRecords(null); contextRevision = null; commandTableRevision = null; @@ -384,6 +388,9 @@ export const useMainDashboardStore = defineStore('mainDashboard', () => { if (patch.reservedGeneralRevision !== undefined) { reservedGeneralRevision.value = patch.reservedGeneralRevision; } + if (patch.reservedGeneralAutorunLimit !== undefined) { + reservedGeneralAutorunLimit.value = patch.reservedGeneralAutorunLimit; + } if (patch.globalRecords !== undefined) { globalRecords.value = structurallyShare(globalRecords.value, patch.globalRecords); lastGeneralRecordId = Math.max(lastGeneralRecordId, patch.globalRecords[0]?.id ?? 0); @@ -425,6 +432,7 @@ export const useMainDashboardStore = defineStore('mainDashboard', () => { patch.boardAccess = boardAccessSnapshot ?? null; patch.reservedGeneralTurns = toRaw(reservedGeneralTurns.value as unknown) as ReservedTurnView[] | null; patch.reservedGeneralRevision = reservedGeneralRevision.value; + patch.reservedGeneralAutorunLimit = reservedGeneralAutorunLimit.value; patch.globalRecords = toRaw(globalRecords.value); patch.generalRecords = toRaw(generalRecords.value); patch.worldHistory = toRaw(worldHistory.value); @@ -552,6 +560,7 @@ export const useMainDashboardStore = defineStore('mainDashboard', () => { generalTurns.turns ) as ReservedTurnView[]; reservedGeneralRevision.value = generalTurns.revision; + reservedGeneralAutorunLimit.value = generalTurns.autorunLimit ?? null; if (records) { applyRecentRecords(records); } @@ -672,6 +681,7 @@ export const useMainDashboardStore = defineStore('mainDashboard', () => { if (generalTurns !== undefined) { patch.reservedGeneralTurns = generalTurns.turns; patch.reservedGeneralRevision = generalTurns.revision; + patch.reservedGeneralAutorunLimit = generalTurns.autorunLimit ?? null; } if (records) { const nextGlobalRecords = mergeRecentRecords(globalRecords.value, records.global); @@ -836,12 +846,14 @@ export const useMainDashboardStore = defineStore('mainDashboard', () => { }); reservedGeneralTurns.value = result.turns; reservedGeneralRevision.value = result.revision; + reservedGeneralAutorunLimit.value = result.autorunLimit ?? null; } catch (err) { error.value = resolveErrorMessage(err); const snapshot = await trpc.turns.reserved.getGeneral.query({ generalId: id }).catch(() => null); if (snapshot) { reservedGeneralTurns.value = snapshot.turns; reservedGeneralRevision.value = snapshot.revision; + reservedGeneralAutorunLimit.value = snapshot.autorunLimit ?? null; } } }; @@ -859,12 +871,14 @@ export const useMainDashboardStore = defineStore('mainDashboard', () => { }); reservedGeneralTurns.value = result.turns; reservedGeneralRevision.value = result.revision; + reservedGeneralAutorunLimit.value = result.autorunLimit ?? null; } catch (err) { error.value = resolveErrorMessage(err); const snapshot = await trpc.turns.reserved.getGeneral.query({ generalId: id }).catch(() => null); if (snapshot) { reservedGeneralTurns.value = snapshot.turns; reservedGeneralRevision.value = snapshot.revision; + reservedGeneralAutorunLimit.value = snapshot.autorunLimit ?? null; } } }; @@ -882,12 +896,14 @@ export const useMainDashboardStore = defineStore('mainDashboard', () => { }); reservedGeneralTurns.value = result.turns; reservedGeneralRevision.value = result.revision; + reservedGeneralAutorunLimit.value = result.autorunLimit ?? null; } catch (err) { error.value = resolveErrorMessage(err); const snapshot = await trpc.turns.reserved.getGeneral.query({ generalId: id }).catch(() => null); if (snapshot) { reservedGeneralTurns.value = snapshot.turns; reservedGeneralRevision.value = snapshot.revision; + reservedGeneralAutorunLimit.value = snapshot.autorunLimit ?? null; } } }; @@ -903,12 +919,14 @@ export const useMainDashboardStore = defineStore('mainDashboard', () => { }); reservedGeneralTurns.value = result.turns; reservedGeneralRevision.value = result.revision; + reservedGeneralAutorunLimit.value = result.autorunLimit ?? null; } catch (err) { error.value = resolveErrorMessage(err); const snapshot = await trpc.turns.reserved.getGeneral.query({ generalId: id }).catch(() => null); if (snapshot) { reservedGeneralTurns.value = snapshot.turns; reservedGeneralRevision.value = snapshot.revision; + reservedGeneralAutorunLimit.value = snapshot.autorunLimit ?? null; } } }; @@ -1192,6 +1210,7 @@ export const useMainDashboardStore = defineStore('mainDashboard', () => { messageContacts, boardAccess, reservedGeneralTurns, + reservedGeneralAutorunLimit, globalRecords, generalRecords, worldHistory, diff --git a/app/game-frontend/src/views/MainView.vue b/app/game-frontend/src/views/MainView.vue index df777c8d..33fbe570 100644 --- a/app/game-frontend/src/views/MainView.vue +++ b/app/game-frontend/src/views/MainView.vue @@ -46,6 +46,7 @@ const { messages, boardAccess, reservedGeneralTurns, + reservedGeneralAutorunLimit, globalRecords, generalRecords, worldHistory, @@ -218,6 +219,7 @@ watch( :current-year="lobbyInfo?.year" :current-month="lobbyInfo?.month" :turn-term-minutes="lobbyInfo?.turnTerm" + :autorun-limit="reservedGeneralAutorunLimit" @set-general-turns="reserveGeneralTurns" @shift-general-turns="shiftGeneralTurns" @repeat-general-turns="repeatGeneralTurns" @@ -341,6 +343,7 @@ watch( :current-year="lobbyInfo?.year" :current-month="lobbyInfo?.month" :turn-term-minutes="lobbyInfo?.turnTerm" + :autorun-limit="reservedGeneralAutorunLimit" @set-general-turns="reserveGeneralTurns" @shift-general-turns="shiftGeneralTurns" @repeat-general-turns="repeatGeneralTurns" @@ -571,6 +574,7 @@ button { .layout-desktop { display: grid; grid-template-columns: repeat(10, minmax(0, 1fr)); + grid-template-rows: 520px minmax(125px, auto) auto; gap: 0; align-items: start; } @@ -584,32 +588,30 @@ button { .layout-desktop > [data-main-target='commands'] { grid-column: 8 / 11; - grid-row: 1; - height: 645px; + grid-row: 1 / 3; + min-height: 645px; width: 290px; margin-left: 10px; - overflow-y: auto; + overflow: visible; } .layout-desktop > [data-main-target='city'] { grid-column: 1 / 8; - grid-row: 1; + grid-row: 2; + align-self: stretch; min-height: 125px; - margin-top: 520px; } .layout-desktop > [data-main-target='nation'] { grid-column: 1 / 6; - grid-row: 1; + grid-row: 3; min-height: 193px; - margin-top: 645px; } .layout-desktop > [data-main-target='general'] { grid-column: 6 / 11; - grid-row: 1; + grid-row: 3; min-height: 193px; - margin-top: 645px; } .layout-desktop > [data-main-target], @@ -719,8 +721,8 @@ button { } .layout-mobile [data-main-target='commands'] { - height: 645px; - overflow-y: auto; + min-height: 645px; + overflow: visible; } .layout-mobile [data-main-target='nation'],