[Submit] Editor role + invite flows (Wave 2)#90995
Conversation
- Force editor role for new members on Submit workspaces in addMembersToWorkspace (FE override) - Add Editor to WorkspaceMemberRoleList (only shown on Submit workspaces) - Add new editors to the #admins room in buildAddMembersToWorkspaceOnyxData - Default the invite role to Editor on Submit workspaces in the invite message + role pages - All Submit-specific behavior is gated behind the SUBMIT_2026 beta, threaded as a parameter - Add common.editor and workspace.common.editorAlternateText (en only; other locales to follow) - Add unit tests covering the editor override, beta gating, and #admins room membership
|
Hey, I noticed you changed If you want to automatically generate translations for other locales, an Expensify employee will have to:
Alternatively, if you are an external contributor, you can run the translation script locally with your own OpenAI API key. To learn more, try running: npx ts-node ./scripts/generateTranslations.ts --helpTypically, you'd want to translate only what you changed by running |
🦜 Polyglot Parrot! 🦜Squawk! Looks like you added some shiny new English strings. Allow me to parrot them back to you in other tongues: View the translation diffdiff --git a/src/languages/de.ts b/src/languages/de.ts
index d47ee109..7d948f43 100644
--- a/src/languages/de.ts
+++ b/src/languages/de.ts
@@ -494,6 +494,7 @@ const translations: TranslationDeepObject<typeof en> = {
previousYear: 'Vorheriges Jahr',
nextYear: 'Nächstes Jahr',
avatar: 'Avatar',
+ editor: 'Editor',
},
socials: {
podcast: 'Folgen Sie uns auf Podcast',
@@ -4321,6 +4322,8 @@ ${amount} für ${merchant} – ${date}`,
return 'Admin';
case CONST.POLICY.ROLE.AUDITOR:
return 'Prüfer';
+ case CONST.POLICY.ROLE.EDITOR:
+ return 'Editor';
case CONST.POLICY.ROLE.USER:
return 'Mitglied';
default:
@@ -4356,6 +4359,7 @@ ${amount} für ${merchant} – ${date}`,
travelInvoicingPayableAccount: 'Reiseverbindlichkeitskonto',
hr: 'Personalwesen',
rooms: 'Räume',
+ editorAlternateText: 'Arbeitsbereichseinstellungen konfigurieren, ohne die Ausgaben anderer Mitglieder zu sehen.',
},
createdForClient: {
title: 'Du hast einen Workspace für deinen Kunden erstellt!',
diff --git a/src/languages/es.ts b/src/languages/es.ts
index 2fce892d..79874074 100644
--- a/src/languages/es.ts
+++ b/src/languages/es.ts
@@ -446,6 +446,7 @@ const translations: TranslationDeepObject<typeof en> = {
expensifyLogo: 'Logo de Expensify',
approver: 'Aprobador',
enterDigitLabel: ({digitIndex, totalDigits}: {digitIndex: number; totalDigits: number}) => `introducir dígito ${digitIndex} de ${totalDigits}`,
+ editor: 'Editor',
},
socials: {
podcast: 'Síguenos en Podcast',
@@ -4199,12 +4200,14 @@ ${amount} para ${merchant} - ${date}`,
[CONST.POLICY.REIMBURSEMENT_CHOICES.REIMBURSEMENT_NO]: 'Ninguno',
[CONST.POLICY.REIMBURSEMENT_CHOICES.REIMBURSEMENT_MANUAL]: 'Indirecto',
},
- roleName: (role) => {
+ roleName: (role?: string) => {
switch (role) {
case CONST.POLICY.ROLE.ADMIN:
- return 'Administrador';
+ return 'Admin';
case CONST.POLICY.ROLE.AUDITOR:
return 'Auditor';
+ case CONST.POLICY.ROLE.EDITOR:
+ return 'Editor';
case CONST.POLICY.ROLE.USER:
return 'Miembro';
default:
@@ -4242,6 +4245,7 @@ ${amount} para ${merchant} - ${date}`,
travelInvoicing: 'Exportar Viajes de Expensify por Pagar a',
travelInvoicingVendor: 'Proveedor de viajes',
travelInvoicingPayableAccount: 'Cuenta por pagar de viajes',
+ editorAlternateText: 'Configura los ajustes del espacio de trabajo sin ver los gastos de otros miembros.',
},
createdForClient: {
title: '¡Has creado un espacio de trabajo para tu cliente!',
diff --git a/src/languages/fr.ts b/src/languages/fr.ts
index 3c89403e..f7552eec 100644
--- a/src/languages/fr.ts
+++ b/src/languages/fr.ts
@@ -494,6 +494,7 @@ const translations: TranslationDeepObject<typeof en> = {
previousYear: 'Année précédente',
nextYear: 'L’an prochain',
avatar: 'Avatar',
+ editor: 'Éditeur',
},
socials: {
podcast: 'Suivez-nous sur Podcast',
@@ -4330,6 +4331,8 @@ ${amount} pour ${merchant} - ${date}`,
return 'Administrateur';
case CONST.POLICY.ROLE.AUDITOR:
return 'Auditeur';
+ case CONST.POLICY.ROLE.EDITOR:
+ return 'Éditeur';
case CONST.POLICY.ROLE.USER:
return 'Membre';
default:
@@ -4365,6 +4368,7 @@ ${amount} pour ${merchant} - ${date}`,
travelInvoicingPayableAccount: 'Compte fournisseur déplacements',
hr: 'RH',
rooms: 'Salons',
+ editorAlternateText: 'Configurer les paramètres de l’espace de travail sans voir les dépenses des autres membres.',
},
createdForClient: {
title: 'Vous avez créé un espace de travail pour votre client !',
diff --git a/src/languages/it.ts b/src/languages/it.ts
index 1edee698..99aeadf5 100644
--- a/src/languages/it.ts
+++ b/src/languages/it.ts
@@ -494,6 +494,7 @@ const translations: TranslationDeepObject<typeof en> = {
previousYear: 'Anno precedente',
nextYear: "L'anno prossimo",
avatar: 'Avatar',
+ editor: 'Editor',
},
socials: {
podcast: 'Seguici su Podcast',
@@ -4307,6 +4308,8 @@ ${amount} per ${merchant} - ${date}`,
return 'Amministrazione';
case CONST.POLICY.ROLE.AUDITOR:
return 'Revisore';
+ case CONST.POLICY.ROLE.EDITOR:
+ return 'Editor';
case CONST.POLICY.ROLE.USER:
return 'Membro';
default:
@@ -4342,6 +4345,7 @@ ${amount} per ${merchant} - ${date}`,
travelInvoicingPayableAccount: 'Conto debiti per viaggi',
hr: 'Risorse umane',
rooms: 'Stanze',
+ editorAlternateText: 'Configura le impostazioni dello spazio di lavoro senza vedere le spese degli altri membri.',
},
createdForClient: {
title: 'Hai creato uno spazio di lavoro per il tuo cliente!',
diff --git a/src/languages/ja.ts b/src/languages/ja.ts
index 1dc8f623..4b1b743b 100644
--- a/src/languages/ja.ts
+++ b/src/languages/ja.ts
@@ -493,6 +493,7 @@ const translations: TranslationDeepObject<typeof en> = {
previousYear: '前年',
nextYear: '来年',
avatar: 'アバター',
+ editor: '編集者',
},
socials: {
podcast: 'ポッドキャストでフォロー',
@@ -4273,7 +4274,9 @@ ${integrationName === CONST.ONBOARDING_ACCOUNTING_MAPPING.other ? 'あなたの'
case CONST.POLICY.ROLE.ADMIN:
return '管理者';
case CONST.POLICY.ROLE.AUDITOR:
- return '監査人';
+ return '監査担当者';
+ case CONST.POLICY.ROLE.EDITOR:
+ return '編集者';
case CONST.POLICY.ROLE.USER:
return 'メンバー';
default:
@@ -4309,6 +4312,7 @@ ${integrationName === CONST.ONBOARDING_ACCOUNTING_MAPPING.other ? 'あなたの'
travelInvoicingPayableAccount: '旅費未払金勘定',
hr: '人事',
rooms: 'ルーム',
+ editorAlternateText: '他のメンバーの経費を表示せずに、ワークスペース設定を構成できます。',
},
createdForClient: {
title: 'クライアントのワークスペースを作成しました!',
diff --git a/src/languages/nl.ts b/src/languages/nl.ts
index 9737a63f..6c02fe96 100644
--- a/src/languages/nl.ts
+++ b/src/languages/nl.ts
@@ -493,6 +493,7 @@ const translations: TranslationDeepObject<typeof en> = {
previousYear: 'Vorig jaar',
nextYear: 'Volgend jaar',
avatar: 'Avatar',
+ editor: 'Editor',
},
socials: {
podcast: 'Volg ons op Podcast',
@@ -4299,9 +4300,11 @@ ${amount} voor ${merchant} - ${date}`,
roleName: (role?: string) => {
switch (role) {
case CONST.POLICY.ROLE.ADMIN:
- return 'Beheerder';
+ return 'Beheer';
case CONST.POLICY.ROLE.AUDITOR:
return 'Auditor';
+ case CONST.POLICY.ROLE.EDITOR:
+ return 'Editor';
case CONST.POLICY.ROLE.USER:
return 'Lid';
default:
@@ -4337,6 +4340,7 @@ ${amount} voor ${merchant} - ${date}`,
travelInvoicingPayableAccount: 'Reiscrediteurenrekening',
hr: 'HR',
rooms: 'Kamers',
+ editorAlternateText: 'Configureer werkruimte-instellingen zonder de onkosten van andere leden te zien.',
},
createdForClient: {
title: 'Je hebt een werkruimte voor je klant aangemaakt!',
diff --git a/src/languages/pl.ts b/src/languages/pl.ts
index fb15ccba..70a4868e 100644
--- a/src/languages/pl.ts
+++ b/src/languages/pl.ts
@@ -493,6 +493,7 @@ const translations: TranslationDeepObject<typeof en> = {
previousYear: 'Poprzedni rok',
nextYear: 'W przyszłym roku',
avatar: 'Avatar',
+ editor: 'Edytor',
},
socials: {
podcast: 'Śledź nas na Podcast',
@@ -4295,6 +4296,8 @@ ${amount} dla ${merchant} - ${date}`,
return 'Administrator';
case CONST.POLICY.ROLE.AUDITOR:
return 'Audytor';
+ case CONST.POLICY.ROLE.EDITOR:
+ return 'Edytor';
case CONST.POLICY.ROLE.USER:
return 'Członek';
default:
@@ -4330,6 +4333,7 @@ ${amount} dla ${merchant} - ${date}`,
travelInvoicingPayableAccount: 'Konto zobowiązań z tytułu podróży',
hr: 'HR',
rooms: 'Pokoje',
+ editorAlternateText: 'Konfiguruj ustawienia przestrzeni roboczej bez podglądu wydatków innych członków.',
},
createdForClient: {
title: 'Utworzyłeś przestrzeń roboczą dla swojego klienta!',
diff --git a/src/languages/pt-BR.ts b/src/languages/pt-BR.ts
index 018d4b28..c7832bf5 100644
--- a/src/languages/pt-BR.ts
+++ b/src/languages/pt-BR.ts
@@ -492,6 +492,7 @@ const translations: TranslationDeepObject<typeof en> = {
previousYear: 'Ano anterior',
nextYear: 'Ano que vem',
avatar: 'Avatar',
+ editor: 'Editor',
},
socials: {
podcast: 'Siga-nos no Podcast',
@@ -4297,6 +4298,8 @@ ${amount} para ${merchant} - ${date}`,
return 'Admin';
case CONST.POLICY.ROLE.AUDITOR:
return 'Auditor';
+ case CONST.POLICY.ROLE.EDITOR:
+ return 'Editor';
case CONST.POLICY.ROLE.USER:
return 'Membro';
default:
@@ -4332,6 +4335,7 @@ ${amount} para ${merchant} - ${date}`,
travelInvoicingPayableAccount: 'Conta a pagar de viagens',
hr: 'RH',
rooms: 'Salas',
+ editorAlternateText: 'Configure as configurações do espaço de trabalho sem ver as despesas de outros membros.',
},
createdForClient: {
title: 'Você criou um espaço de trabalho para seu cliente!',
diff --git a/src/languages/zh-hans.ts b/src/languages/zh-hans.ts
index 7f51cc44..10041e4f 100644
--- a/src/languages/zh-hans.ts
+++ b/src/languages/zh-hans.ts
@@ -489,6 +489,7 @@ const translations: TranslationDeepObject<typeof en> = {
previousYear: '上一年',
nextYear: '明年',
avatar: '头像',
+ editor: '编辑器',
},
socials: {
podcast: '在播客上关注我们',
@@ -4206,6 +4207,8 @@ ${amount},商户:${merchant} - 日期:${date}`,
return '管理员';
case CONST.POLICY.ROLE.AUDITOR:
return '审计员';
+ case CONST.POLICY.ROLE.EDITOR:
+ return '编辑器';
case CONST.POLICY.ROLE.USER:
return '成员';
default:
@@ -4240,6 +4243,7 @@ ${amount},商户:${merchant} - 日期:${date}`,
travelInvoicingPayableAccount: '差旅应付账户',
hr: '人力资源',
rooms: '房间',
+ editorAlternateText: '在不查看其他成员报销的情况下配置工作区设置。',
},
createdForClient: {
title: '您已为客户创建了工作区!',
Note You can apply these changes to your branch by copying the patch to your clipboard, then running |
… Profile WorkspaceMembersPage previously bailed to ROUTES.PROFILE whenever the policy was not isPaidGroupPolicy. Submit (submit2026) workspaces are intentionally excluded from isPaidGroupPolicy, which made editors unable to reach the member-details page (and therefore the Role row) for other members. Add a canManageMembers check that includes Submit workspaces (gated on the SUBMIT_2026 beta) so editors land on WORKSPACE_MEMBER_DETAILS like they do on Collect/Control.
The Editor description ("Configure workspace settings without seeing other
members' expenses.") was being truncated to a single line. Match the role
list pattern used elsewhere (e.g. UpdateDelegateRolePage, SelectionScreen)
by setting alternateNumberOfSupportedLines={2}.
Previously the role list on a Submit workspace showed Editor + Member as options. Picking Member was misleading: the FE override in addMembersToWorkspace and the backend both force the role back to Editor on submit2026 policies, so the user got Editor regardless. Filter Member out alongside Admin/Auditor when the policy is Submit and the SUBMIT_2026 beta is on — Editor is the only valid role for invites and role-changes there.
Editor is the only valid role on a Submit workspace, so there's nothing for the user to choose. Mark the (filtered) Editor item as isInteractive: false so the row stays visible and informative but no longer responds to taps.
|
@MelvinBot review the changes find regressions or breaking issues |
PR Review — Potential Regressions & Issues1. Missing
|
Codecov Report❌ Looks like you've decreased code coverage for some files. Please write tests to increase, or at least maintain, the existing level of code coverage. See our documentation here for how to interpret this table.
|
…2026 through changeReportPolicyAndInviteSubmitter - buildAddMembersToWorkspaceOnyxData is now the single source of truth for the editor-role override on Submit workspaces; it returns the effective role so addMembersToWorkspace can use it for API params without duplicating the conditional. - Simplify shouldAddToAdminsRoom: shouldForceEditorRole already implies effectiveRole === EDITOR, so drop the redundant equality check. - Thread canUseSubmit2026 from ReportChangeWorkspacePage through changeReportPolicyAndInviteSubmitter so moving a report into a Submit workspace shows the correct optimistic role (no Member->Editor flash).
Admin doesn't exist on Submit workspaces per design — only Editor does. Previously, if a user's role on a Submit policy happened to be Admin (e.g. an inconsistent server response), the Admin row would still render in the role list (just non-interactive after the read-only map). Filter it out unconditionally on Submit so the list only ever shows Editor.
…; fix zh-hans Editor The auto-translation diff accidentally renamed pre-existing role names across other locales. Revert these so this PR only adds the new Editor strings and doesn't change what existing users see for other roles: - es: Administrador (keep — was renamed to Admin) - nl: Beheerder (keep — was renamed to Beheer) - ja: 監査人 (keep — was renamed to 監査担当者) Also fix the zh-hans Editor translation: - common.editor 编辑器 -> 编辑 (编辑器 is a software 'editor', not the person role) - workspace.common.roleName(EDITOR) 编辑器 -> 编辑
- Add EDITOR case to MemberRightIcon so the role badge renders on the members list for Submit workspace editors. - Simplify openMemberDetails routing in WorkspaceMembersPage: use canEditWorkspaceSettings (already true for editors) instead of the beta-gated canManageMembers, fixing the regression where clicking a member row opened the public Profile page instead of WORKSPACE_MEMBER_DETAILS.
The beta gates Submit workspace creation; it should not affect the role list UI of an existing submit2026 workspace. Derive isPolicySubmit2026 directly from isSubmitPolicy(policy) so the Editor-only role list renders correctly even when the SUBMIT_2026 beta is not active in the current session.
… details The role is locked to Editor on Submit workspaces — there is nothing to select. Marking the row as non-interactive (no chevron, no onPress) prevents accidental navigation to WorkspaceMemberDetailsRolePage. The full paywall/upgrade redirect for Roles is handled separately in the Wave 2 paywall-gating issue.
…-role-invite-flows
|
Observation: workspace creator role on Submit workspaces Per the design doc, the workspace creator should be an editor (owner), not an admin — the Admin role does not exist on Submit workspaces. Everyone including the creator should have the Editor role. I noticed that the backend returns: Screen.Recording.2026-05-19.at.05.43.10.movIf the backend is returning |
|
@MelvinBot Review the PR find other issues if any |
Additional Review FindingsFollowing up on my earlier review, here are additional issues found on a deeper pass: 6. Missing Editor role badge in members list
QA step 4 says "verify User B is shown as Editor" — but there's no visual indicator on the members list to confirm this (only the member detail page would show it via If this is intentional (matching how } else if (policyEmployee.role === CONST.POLICY.ROLE.EDITOR) {
roleBadgeText = translate('common.editor');
}7. No "Editors" option in the role filter dropdown
If all Submit members are Editors, this filter may be unnecessary — but it's a gap if admins exist alongside editors on a Submit workspace and the user wants to filter. 8.
|
|
Backend bug: invited editors' roles reset to Member on re-login After inviting a member to a Submit workspace (role correctly shows as Editor optimistically), logging out and back in causes the backend to return those members with Screen.Recording.2026-05-19.at.05.56.55.movLooks like its part of the Wave 1 backend task — "Update Policy::shareWithEmployees — always invite users to submit2026 policies as the editor role" — was it already implemented? |
#6 — Add Editor case to roleBadgeText in WorkspaceMembersPage so the accessibility label includes 'Editor' for Submit workspace members (the visual badge was already correct via MemberRightIcon). #8 — Guard updateWorkspaceMembersRole against Submit workspaces. The UI already blocks role changes via isInteractive:false and isPaidGroupPolicy checks, but the action itself was unguarded. This closes the gap against future programmatic call sites.
|
@hungvu193 Please copy/paste the Reviewer Checklist from here into a new comment on this PR and complete it. If you have the K2 extension, you can simply click: [this button] |
|
The PR is ready |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 696bfc1a9d
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
|
note for myself ... after this PR is merged #87833 we should make sure that the role shows the upgrade modal correctly after the current changes |
| } | ||
| // Editor and Member are mutually exclusive across plan types: Submit workspaces only allow Editor | ||
| // (Member would be a misleading no-op since the backend forces Editor), and other plans only allow | ||
| // Member (Editor doesn't exist there). |
There was a problem hiding this comment.
Not sure the Member comment is necessary past the first sentence.
| shouldSingleExecuteRowSelect | ||
| initiallyFocusedItemKey={availableRoleItems.find((item) => item.isSelected)?.keyForList} | ||
| addBottomSafeAreaPadding | ||
| alternateNumberOfSupportedLines={2} |
| // caller passed. This matches the backend's behavior in Policy::shareWithEmployees so the optimistic | ||
| // updates don't briefly show the wrong role. Gated on the beta so behavior is a no-op for users without it. | ||
| const shouldForceEditorRole = canUseSubmit2026 && isSubmitPolicy(policy); | ||
| const effectiveRole = shouldForceEditorRole ? CONST.POLICY.ROLE.EDITOR : role; |
There was a problem hiding this comment.
I don't think we need shouldForceEditorRole, we can do const effectiveRole = canUseSubmit2026 && isSubmitPolicy(policy) ? CONST.POLICY.ROLE.EDITOR : role; and just compare against CONST.POLICY.ROLE.EDITOR below.
| role === CONST.POLICY.ROLE.ADMIN || role === CONST.POLICY.ROLE.AUDITOR ? accountIDs : [], | ||
| ); | ||
| // Admins and auditors are always in the #admins room. Editors (Submit workspaces only) join it too so they get | ||
| // visibility into configuration changes — gated on the beta to keep the room membership unchanged for non-beta users. |
There was a problem hiding this comment.
| // visibility into configuration changes — gated on the beta to keep the room membership unchanged for non-beta users. | |
| // visibility into configuration changes. |
| // Submit workspaces only allow the editor role on invite — `buildAddMembersToWorkspaceOnyxData` handles | ||
| // the override (and returns the effective role) so the optimistic data and API request stay in sync. | ||
| // The backend enforces this too (Policy::shareWithEmployees); the FE override is just to avoid a flash | ||
| // of the caller-provided role in the UI. |
There was a problem hiding this comment.
The comment above could be more concise.
| const openMemberDetails = useCallback( | ||
| (item: MemberOption) => { | ||
| if (!isPolicyAdmin || !isPaidGroupPolicy(policy)) { | ||
| if (!isPolicyAdmin) { |
There was a problem hiding this comment.
Should this be behind the beta?
- WorkspaceMemberRoleList: shorten redundant comment; annotate
alternateNumberOfSupportedLines={2} to explain why it is needed
- Policy/Member: remove shouldForceEditorRole variable per suggestion;
inline condition as effectiveRole ternary; trim two verbose comments
- WorkspaceMembersPage: beta-gate openMemberDetails routing — restore
canManageMembers guard (isPaidGroupPolicy || (canUseSubmit2026 &&
isSubmitPolicy)) so Submit-specific routing change is a no-op when
the beta is disabled
…/87865-editor-role-invite-flows # Conflicts: # src/languages/de.ts # src/languages/en.ts # src/languages/es.ts # src/languages/fr.ts # src/languages/it.ts # src/languages/ja.ts # src/languages/nl.ts # src/languages/pl.ts # src/languages/pt-BR.ts # src/languages/zh-hans.ts # src/libs/actions/Policy/Member.ts # src/pages/workspace/DynamicWorkspaceInviteMessageRolePage.tsx # src/pages/workspace/members/WorkspaceInviteMessageComponent.tsx
|
I am experiencing an issue where the backend doesn't return the invited user in the list in case of a submit workspace is it a problem with the Screen.Recording.2026-05-23.at.00.58.25.mov |
|
@abzokhattab got a PR up to fix it. |

Explanation of Change
Wires up the Editor role + invite flows task from Wave 2 of the Bottom-Up Submit Plan:
addMembersToWorkspace(matches the backend'sPolicy::shareWithEmployeesbehavior).WorkspaceMemberRoleList, shown only on Submit workspaces. Admin stays gated behindisPolicyAdmin, so editors cannot escalate roles.#adminsroom inbuildAddMembersToWorkspaceOnyxData.WorkspaceInviteMessageComponentandDynamicWorkspaceInviteMessageRolePage.SUBMIT_2026beta — resolved viausePermissions()and threaded down as acanUseSubmit2026param so the action stays pure. Beta off = no-op.common.editorandworkspace.common.editorAlternateTexttoen.ts(other locales follow up).Fixed Issues
$ #87865
PROPOSAL: N/A (internal Expensify project — design doc tracked in #87865)
Tests
Same as QA Steps.
Offline tests
N/A — the editor role override is applied optimistically and the existing offline / optimistic-data behavior of
addMembersToWorkspaceis preserved.QA Steps
PR Author Checklist
Screenshots/Videos
Android: Native
Screen.Recording.2026-05-19.at.06.05.36.mov
Android: mWeb Chrome
Screen.Recording.2026-05-19.at.06.08.23.mov
iOS: Native
Screen.Recording.2026-05-19.at.05.52.26.mov
iOS: mWeb Safari
Screen.Recording.2026-05-19.at.05.54.55.mov
MacOS: Chrome / Safari
Screen.Recording.2026-05-19.at.05.39.32.mov