fix(manage-sheet): clearer copy + visual hierarchy in row controls
CI / test (push) Has been cancelled

Matt called out the row was confusing: lock pill said "Rotate" (sounds
like a verb), and the toggle's purpose wasn't obvious.

  - Drop the "Rotate" word entirely. Lock pill is icon-only when
    unlocked, shows "Locked" + closed padlock when locked.
  - Hide the lock pill entirely when the photo isn't approved on the
    frame (instead of rendering a disabled one) — keeps the row clean
    and reinforces that locking requires approval first.
  - Add a tiny "Show" / "Hidden" label above the toggle so the meaning
    reads before the user taps. Toggle is now the visual primary on
    the row.
  - Re-label aria-text from "Add/Remove" to "Show/Hide" to match the
    visible copy.

Test "disables the lock pill when not approved" → "hides the lock pill
when not approved". 358/358 still passing.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-14 15:31:07 -04:00
parent 84642ed13f
commit 45e80cf4c0
11 changed files with 53 additions and 23 deletions
+33 -4
View File
@@ -25,11 +25,16 @@
<span class="manage__device-meta">{{ device.orientation }}</span>
</div>
<!-- Lock control. Hidden entirely when the photo isn't approved on
this frame — no need to show a disabled lock when it can't
apply. When locked, shows "Locked" + closed padlock; otherwise
icon-only so the toggle stays the visual primary. -->
<button
v-if="isApproved(device.id)"
type="button"
class="manage__lock"
:class="{ 'manage__lock--on': device.lockedImageId === image?.id }"
:disabled="!isApproved(device.id) || pendingLock === device.id"
:disabled="pendingLock === device.id"
:aria-label="device.lockedImageId === image?.id
? `Unlock from ${device.name}`
: `Lock to ${device.name}`"
@@ -40,19 +45,25 @@
<path v-if="device.lockedImageId === image?.id" d="M7 11V7a5 5 0 0 1 10 0v4"/>
<path v-else d="M7 11V7a5 5 0 0 1 9.9-1"/>
</svg>
<span>{{ device.lockedImageId === image?.id ? 'Locked' : 'Rotate' }}</span>
<span v-if="device.lockedImageId === image?.id">Locked</span>
</button>
<!-- Show / hide this photo on this frame. Primary control on the row. -->
<label class="manage__toggle-wrap">
<span class="manage__toggle-label">
{{ isApproved(device.id) ? 'Show' : 'Hidden' }}
</span>
<button
type="button"
class="manage__toggle"
:class="{ 'manage__toggle--on': isApproved(device.id) }"
:disabled="pendingApproval === device.id"
:aria-label="isApproved(device.id)
? `Remove this photo from ${device.name}`
: `Add this photo to ${device.name}`"
? `Hide this photo from ${device.name}`
: `Show this photo on ${device.name}`"
@click="onApprovalClick(device)"
></button>
</label>
</div>
</div>
@@ -195,6 +206,24 @@ function onLockClick(device: Device) {
}
}
// Show/hide toggle group — label above the slider so the meaning is
// obvious before tapping.
&__toggle-wrap {
display: flex;
flex-direction: column;
align-items: center;
gap: 2px;
cursor: pointer;
flex-shrink: 0;
}
&__toggle-label {
font-size: var(--text-xs);
font-weight: 600;
color: var(--color-text-muted);
user-select: none;
}
// iOS-style approval toggle
&__toggle {
width: 48px;
@@ -94,13 +94,14 @@ describe('ManageImageSheet', () => {
expect(w.emitted('approval')![0][0]).toEqual({ imageId: 7, deviceId: 4, approved: false })
})
it('disables the lock pill when the image is not approved on the device', () => {
it('hides the lock pill when the image is not approved on the device', () => {
// The pill can't apply when the photo isn't approved on the frame —
// hiding (rather than disabling) keeps the row visually clean.
const w = mountSheet({
image: makeImage({ approvedDeviceIds: [] }),
devices: [makeDevice({ id: 1 })],
})
const lock = w.find('.manage__lock')
expect(lock.attributes('disabled')).toBeDefined()
expect(w.find('.manage__lock').exists()).toBe(false)
})
it('shows the lock pill in --on state when the device is locked to this image', () => {
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -14,7 +14,7 @@
<meta name="mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="default" />
<meta name="apple-mobile-web-app-title" content="pictureFrame" />
<script type="module" crossorigin src="/build/assets/index-Ds9OAB3e.js"></script>
<script type="module" crossorigin src="/build/assets/index-DuLafD-s.js"></script>
<link rel="modulepreload" crossorigin href="/build/assets/_plugin-vue_export-helper-BNDVmFr7.js">
<link rel="stylesheet" crossorigin href="/build/assets/index-BlLBHR1q.css">
</head>