Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
2e4422f
fix(copy): say company, not workspace (QA sweep COPY-01)
NCHILLC Oct 13, 2026
4c9bb07
fix(copy): login network error no longer asks about the API server (Q…
NCHILLC Oct 14, 2026
fbdb36b
fix(copy): replace Cross-tenant view with plain wording (QA sweep COP…
NCHILLC Oct 14, 2026
f03c9ec
fix(copy): sentence case for the cited button pairs (QA sweep COPY-04)
NCHILLC Oct 14, 2026
b90592e
fix(copy): straight apostrophes throughout the catalogs (QA sweep COP…
NCHILLC Oct 14, 2026
4bebfde
fix(copy): use the ellipsis character in place of three dots (QA swee…
NCHILLC Oct 14, 2026
53fd767
fix(copy): generic errors say what failed and what to do (QA sweep CO…
NCHILLC Oct 14, 2026
d2f0b44
fix(copy): drop tenant, D1, SaaS and payload jargon from user-facing …
NCHILLC Oct 14, 2026
89a22bc
fix(copy): link ID instead of slug (QA sweep COPY-09)
NCHILLC Oct 14, 2026
e46be65
fix(copy): Repair Items instead of Recommendations (QA sweep COPY-10)
NCHILLC Oct 14, 2026
df04ff1
fix(copy): Inspection instead of Order (QA sweep COPY-11)
NCHILLC Oct 14, 2026
a0079f8
fix(copy): Clients instead of Customers (QA sweep COPY-12)
NCHILLC Oct 14, 2026
a16ff26
fix(copy): Document ID instead of Envelope ID (QA sweep COPY-13)
NCHILLC Oct 14, 2026
8e032fd
fix(copy): settle on Accept invitation (QA sweep COPY-14)
NCHILLC Oct 14, 2026
51bc69e
docs(fork-log): row for the COPY pass
NCHILLC Oct 14, 2026
f24010a
test(copy): update assertions to the new catalog strings (QA sweep COPY)
NCHILLC Oct 14, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion app/components/editor/property-info-form.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -309,7 +309,7 @@ describe("PropertyInfoForm — Fetch property details (#200)", () => {

fireEvent.click(screen.getByRole("button", { name: /fetch property details/i }));

await waitFor(() => expect(screen.getByText(/isn.t configured/i)).toBeTruthy());
await waitFor(() => expect(screen.getByText(/is not set up for your company/i)).toBeTruthy());
expect(onCommit).not.toHaveBeenCalled();
});
});
4 changes: 2 additions & 2 deletions app/components/settings/AiAssurancePanel.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -123,9 +123,9 @@ describe("AiAssurancePanel", () => {
expect(screen.getByText("u1")).toBeTruthy();
});

it("reports reviews that cite a call this workspace has no record of", () => {
it("reports reviews that cite a call this company has no record of", () => {
renderPanel(base({ calls: [reviewedCall()], unresolvedReviewCount: 3 }));
expect(screen.getByText(/reviews citing a call this workspace has no record of: 3/i)).toBeTruthy();
expect(screen.getByText(/reviews citing a call this company has no record of: 3/i)).toBeTruthy();
});

it("stays silent when every review resolves", () => {
Expand Down
4 changes: 2 additions & 2 deletions app/components/template/item-comments-panel.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ describe("ItemCommentsPanel (behavior-preserving swap)", () => {
expect(screen.getByDisplayValue("Shingles lifted")).toBeTruthy();
expect(screen.queryByText("Lifted at ridge.")).toBeNull();
fireEvent.click(screen.getByLabelText("Expand comment"));
const textarea = screen.getByPlaceholderText("Comment text...") as HTMLTextAreaElement;
const textarea = screen.getByPlaceholderText("Comment text…") as HTMLTextAreaElement;
expect(textarea.value).toBe("Lifted at ridge.");
});

Expand All @@ -64,7 +64,7 @@ describe("ItemCommentsPanel (behavior-preserving swap)", () => {
// Guard: the CommentTypeahead wiring must not remove the editable textarea.
renderPanel();
fireEvent.click(screen.getByLabelText("Expand comment"));
const textarea = screen.getByPlaceholderText("Comment text...") as HTMLTextAreaElement;
const textarea = screen.getByPlaceholderText("Comment text…") as HTMLTextAreaElement;
expect(textarea.tagName).toBe("TEXTAREA");
expect(textarea.value).toBe("Lifted at ridge.");
});
Expand Down
4 changes: 2 additions & 2 deletions app/lib/connected-apps.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -289,7 +289,7 @@ describe('SettingsConnectedApps component render', () => {
isSaas: false,
} as never);
const html = renderToStaticMarkup(createElement(SettingsConnectedApps));
expect(html).not.toContain('Tenant-wide');
expect(html).not.toContain('Company-wide');
});

it('renders the tenant-wide section for an owner with all-grants data', () => {
Expand All @@ -311,7 +311,7 @@ describe('SettingsConnectedApps component render', () => {
seatUsage: null, outboundCoolingWindow: null,
});
const html = renderToStaticMarkup(createElement(SettingsConnectedApps));
expect(html).toContain('Tenant-wide');
expect(html).toContain('Company-wide');
expect(html).toContain('bob@example.com');
});

Expand Down
6 changes: 3 additions & 3 deletions app/lib/forms/auth.schema.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,15 +112,15 @@ describe('auth login i18n (Phase C pilot)', () => {

it('resolves login UI messages in en (baseLocale)', () => {
overwriteGetLocale(() => 'en');
expect(m.auth_login_heading()).toBe('Log in to your workspace');
expect(m.auth_login_submit()).toBe('Log In');
expect(m.auth_login_heading()).toBe('Log in to your company');
expect(m.auth_login_submit()).toBe('Log in');
expect(m.auth_login_email_label()).toBe('Email address');
});

it('resolves login UI + interpolation messages in es-419', () => {
overwriteGetLocale(() => 'es-419');
// Formal `usted` register — see docs/develop/conventions/i18n-glossary.md.
expect(m.auth_login_heading()).toBe('Inicie sesión en su espacio de trabajo');
expect(m.auth_login_heading()).toBe('Inicie sesión en su empresa');
expect(m.auth_login_submit()).toBe('Iniciar sesión');
// category 2 — server-side interpolation message
expect(m.auth_login_error_failed_with_status({ status: 500 })).toBe(
Expand Down
2 changes: 1 addition & 1 deletion app/lib/settings-integrations.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -232,7 +232,7 @@ describe('settings-integrations action — save-video intent', () => {
expect(patchTenantConfig).not.toHaveBeenCalled();
expect(postAdminConfig).not.toHaveBeenCalled();
expect(res).toMatchObject({ intent: 'save-video', success: false });
expect((res as { error: string }).error).toMatch(/plan-managed/);
expect((res as { error: string }).error).toMatch(/managed by your plan/);
});
});

Expand Down
1 change: 1 addition & 0 deletions docs/develop/fork-log.md
Original file line number Diff line number Diff line change
Expand Up @@ -335,3 +335,4 @@ deliberate departure rather than work upstream simply has not done yet.
| `ContactModal` closes after a save (2026-10-13 QA sweep FORM-01, Major; no upstream fix) | `app/components/contacts/ContactModal.tsx` closes on the fetcher's busy to idle transition with `ok:true`, in an effect, instead of reading `fetcherOk` inside `onSubmit`. Spec: `ContactModal.test.tsx` (proven red without the change) | The finding: the 200 created the contact but the dialog sat open with the form refilled. The old handler read the PREVIOUS result at click time, so it could only close on a second save. `upstream/main` (`435b24ec`) still has that line: its `793a4161` added the busy latch around it and left it. The transition (not the value) is the trigger because the modal stays mounted and an earlier `ok:true` is still in `fetcher.data`. No "Contact added" toast: the new row in the list is the confirmation, and a toast is a new string in both locales for this fork alone |
| `/templates` create dialog stays open until the action answers (2026-10-13 QA sweep FORM-06, Major; no upstream fix) | `handleCreate` in `app/routes/templates.tsx` no longer closes the dialog and clears the name on click; success navigates to the editor, failure renders `error` in the dialog (it was already wired, to a dialog that had been closed). `CreateTemplateModal` gets `maxLength={100}`, the API's limit. Spec: `app/routes/templates.test.tsx` | The finding: a ~330-character name did nothing visible, because the API refused it after the dialog had closed. Checked `upstream/main` for the route and the modal: same close-on-click. **Known ceilings:** no character counter (a new string in both locales), so a typed name just stops at 100; the server's 400 for an over-long name is the generic "could not create" text if it ever gets past `maxLength` (the zod message is not read) |
| Public links choose `http` only for exact loopback hosts (2026-10-13 QA sweep DELIV-02, Major; no upstream fix) | `scheme()` in `server/lib/public-urls.ts` matched `host.startsWith('localhost')`, so a real domain that merely begins with `localhost` or `127.0.0.1` got `http://` on token-bearing agreement, invoice and checkout links. It now compares the hostname exactly (`localhost`, `*.localhost`, `127.0.0.1`, `[::1]`). Spec: `tests/unit/reports/public-urls.spec.ts` (proven red without the change) | The sweep saw `http://127.0.0.1:8791/...` in the emails and the `/v/` QR. That was the local dev host, which is loopback on purpose, and the QR base is `APP_BASE_URL`, which the sweep had set to an `http` address; neither is a production defect. **Known ceiling:** `getBookingHost` still returns a bare host, so the scheme is guessed from the hostname, not read from the request or `APP_BASE_URL`; an operator who sets `APP_BASE_URL` to `http://` on a non-loopback host still gets `https` links from these builders. Threading the scheme through the ~20 callers was not worth it for a loopback-only discrepancy. |
| Copy pass over the English and es-419 catalogs (2026-10-13 QA sweep COPY-01 to COPY-15; no upstream fix, `upstream/main` `435b24ec` only adds feature copy) | Catalog values only, keys unchanged. COPY-01 "workspace" becomes "company" (login heading, validation, nav, settings, notices; the es-419 equivalents become "empresa"); COPY-02 the login network error no longer asks whether the API server is running; COPY-03 "Cross-tenant view" becomes "Every inspector in one view"; COPY-04 the cited button pairs go to sentence case (Create account, Log in, Sign agreement, Save company, Invite member, plus two email CTA defaults); COPY-05 curly apostrophes become straight (the 138 to 23 majority); COPY-06 `...` becomes `…` except the `https://g.page/r/...` placeholder; COPY-07 the seven generic errors say what failed and what to do; COPY-08 tenant, D1, SaaS, payload and backend leave user-facing strings; COPY-09 "slug" becomes "link ID" or "link name"; COPY-10 "Recommendations" becomes "Repair Items" in the agent custom-category note; COPY-11 "Order" becomes "Inspection"; COPY-12 "Customers" becomes "Clients"; COPY-13 "Envelope ID" becomes "Document ID" (catalog and the hard-coded line in `server/api/agreements-render.ts`); COPY-14 "Accept invite" and "Accept invitation" settle on "Accept invitation" | **Known ceilings:** COPY-04 fixes the pairs the finding names, not every Title Case label in the 30 files; a lint rule would be the durable fix and is not written. COPY-15 needed no change: ONB-02 already moved the Cloudflare steps into the collapsed "For whoever deployed this" note. The generic route error boundary in `app/root.tsx` is hard-coded "Something went wrong" and untouched. Message keys keep their old names (`settings_workspace_*`, `agent_report_actions_workspace_*`, `auth_validation_workspace_name_required`, `metrics_kpi_aov`) to keep the diff to values; the es-419 values were edited by the same rule, not re-reviewed by a Spanish speaker |
28 changes: 14 additions & 14 deletions messages/en/auth.json
Original file line number Diff line number Diff line change
@@ -1,24 +1,24 @@
{
"$schema": "https://inlang.com/schema/inlang-message-format",
"auth_login_meta_title": "Log in — OpenInspection",
"auth_login_heading": "Log in to your workspace",
"auth_login_heading": "Log in to your company",
"auth_login_subtitle": "Enter your credentials to access inspections, reports, and team tools.",
"auth_login_email_label": "Email address",
"auth_login_password_label": "Password",
"auth_login_forgot_link": "Forgot password?",
"auth_login_submit": "Log In",
"auth_login_submit": "Log in",
"auth_login_submit_pending": "Logging in…",
"auth_login_error_failed_with_status": "Login failed ({status})",
"auth_login_error_no_token": "Authentication succeeded but no token received",
"auth_login_error_network": "Network error — is the API server running?",
"auth_login_error_network": "We could not reach the server. Check your connection and try again.",
"auth_validation_email_required": "Email is required",
"auth_validation_email_invalid": "Invalid email address",
"auth_validation_password_required": "Password is required",
"auth_validation_password_min8": "Password must be at least 8 characters",
"auth_validation_password_uppercase": "Must contain at least one uppercase letter",
"auth_validation_password_number": "Must contain at least one number",
"auth_validation_password_special": "Must contain at least one special character",
"auth_validation_workspace_name_required": "Workspace name is required",
"auth_validation_workspace_name_required": "Company name is required",
"auth_validation_your_name_required": "Your name is required",
"auth_validation_name_too_long": "Name is too long",
"auth_validation_setup_code_min": "Setup code must be at least 6 characters",
Expand Down Expand Up @@ -51,19 +51,19 @@
"auth_reset_password_label": "New password",
"auth_reset_submit_pending": "Updating…",
"auth_reset_submit": "Update password",
"auth_join_meta_title": "Accept Invite - OpenInspection",
"auth_join_meta_title": "Accept invitation - OpenInspection",
"auth_join_error_missing_token": "Missing invite token",
"auth_join_error_invalid": "Invalid or expired invite link",
"auth_join_error_unavailable": "Service unavailable",
"auth_join_error_accept_failed": "Could not accept invite. The link may have expired.",
"auth_join_error_accept_failed": "Could not accept the invitation. The link may have expired.",
"auth_join_invalid_heading": "Invalid Invite",
"auth_join_heading": "Join {name}",
"auth_join_heading_fallback_name": "the team",
"auth_join_subtitle_invited_as": "You have been invited as {email}. Set your name and password to get started.",
"auth_join_subtitle_invited": "You have been invited. Set your name and password to get started.",
"auth_join_name_label": "Full name",
"auth_join_submit_pending": "Accepting…",
"auth_join_submit": "Accept Invite",
"auth_join_submit": "Accept invitation",
"auth_setup_meta_title": "Setup - OpenInspection",
"auth_setup_error_failed": "Setup failed. Please check your inputs.",
"auth_setup_error_no_session": "Setup succeeded but no session was created",
Expand All @@ -85,12 +85,12 @@
"auth_setup_code_help_suffix": "(type Secret), then refresh this page.",
"auth_setup_code_help_link": "How to add a secret →",
"auth_setup_operator_summary": "For whoever deployed this: the setup code and agent sign-up",
"auth_setup_agent_terms_note": "One thing this page cannot do: agent sign-up stays closed until the agent terms are published. Until then agents are told sign-up is not available, and no workspace administrator can change it — only whoever operates this deployment can publish them, from the command line:",
"auth_setup_agent_terms_note": "One thing this page cannot do: agent sign-up stays closed until the agent terms are published. Until then agents are told sign-up is not available, and no company administrator can change it — only whoever operates this deployment can publish them, from the command line:",
"auth_setup_agent_terms_docs": "See docs/operate/deploy.md. Finish creating your account first — this is a separate step, and nothing here waits on it.",
"auth_setup_submit_pending": "Creating your account…",
"auth_setup_submit": "Create Account",
"auth_setup_submit": "Create account",
"auth_agent_invite_meta_title": "You're invited - OpenInspection",
"auth_agent_invite_error_failed": "Could not accept invite",
"auth_agent_invite_error_failed": "Could not accept the invitation",
"auth_agent_invite_unavailable_heading": "Invite unavailable",
"auth_agent_invite_unavailable_body": "This invite link is expired, already used, or invalid.",
"auth_agent_invite_signup_link": "Sign up directly instead",
Expand All @@ -99,7 +99,7 @@
"auth_agent_invite_body_rest": "has invited you to be a partner agent. See every inspection your inspectors complete for the clients you refer.",
"auth_agent_invite_prop1_title": "Real-time referrals",
"auth_agent_invite_prop1_sub": "See reports the moment they're ready",
"auth_agent_invite_prop2_title": "Cross-tenant view",
"auth_agent_invite_prop2_title": "Every inspector in one view",
"auth_agent_invite_prop2_sub": "All your inspectors, one dashboard",
"auth_agent_invite_prop3_title": "Free",
"auth_agent_invite_prop3_sub": "No fees, no card on file",
Expand All @@ -108,7 +108,7 @@
"auth_agent_name_placeholder": "Jane Smith",
"auth_agent_invite_password_label": "Create a password",
"auth_agent_password_placeholder": "At least 12 characters",
"auth_agent_invite_submit_pending": "Setting up your account...",
"auth_agent_invite_submit_pending": "Setting up your account…",
"auth_agent_invite_submit": "Accept invitation",
"auth_agent_invite_footer_note": "By accepting you agree to receive notifications when your referrals are inspected. You can unsubscribe at any time.",
"auth_agent_signup_meta_title": "Become a partner agent - OpenInspection",
Expand All @@ -126,14 +126,14 @@
"auth_agent_signup_form_subtitle": "Takes about a minute. Already invited? Use the link in your email instead — it pre-fills the right company.",
"auth_agent_signup_email_label": "Work email",
"auth_agent_signup_email_placeholder": "jane@realty.com",
"auth_agent_signup_submit_pending": "Creating account...",
"auth_agent_signup_submit_pending": "Creating account…",
"auth_agent_signup_submit": "Create account",
"auth_agent_signup_have_account": "Already have an account?",
"auth_agent_signup_login_link": "Log in",
"auth_agent_login_meta_title": "Agent sign in - OpenInspection",
"auth_agent_login_heading": "Agent sign in",
"auth_agent_login_subtitle": "Sign in to see referrals, reports, and inspector availability.",
"auth_agent_login_submit": "Log In",
"auth_agent_login_submit": "Log in",
"auth_agent_login_submit_pending": "Logging in…",
"auth_agent_login_error_invalid": "Invalid email or password",
"auth_agent_login_error_no_session": "Authentication succeeded but no session was created",
Expand Down
12 changes: 6 additions & 6 deletions messages/en/booking.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,16 @@
"booking_error_service_unavailable": "Service unavailable",
"booking_error_state_heading": "Not Available",
"booking_error_state_default": "This booking page is not available.",
"booking_not_open_heading": "Online booking isn’t open yet",
"booking_not_open_body": "{company} hasn’t opened online scheduling yet. Please contact them directly to book your inspection.",
"booking_not_open_body_no_contact": "{company} hasn’t opened online scheduling yet. This page will let you book as soon as they do.",
"booking_not_open_heading": "Online booking isn't open yet",
"booking_not_open_body": "{company} hasn't opened online scheduling yet. Please contact them directly to book your inspection.",
"booking_not_open_body_no_contact": "{company} hasn't opened online scheduling yet. This page will let you book as soon as they do.",
"booking_embed_unavailable": "Booking unavailable.",
"booking_inspector_default_name": "Inspector",
"booking_embed_company_default_name": "Inspection company",
"booking_logo_alt": "Logo",
"booking_embed_book_with_heading": "Book with {name}",
"booking_embed_confirm_by_email": "Pick a date and we’ll confirm by email.",
"booking_embed_not_open": "Online booking isn’t open yet — please contact {name} directly to schedule.",
"booking_embed_confirm_by_email": "Pick a date and we'll confirm by email.",
"booking_embed_not_open": "Online booking isn't open yet — please contact {name} directly to schedule.",
"booking_embed_status_success": "Booking request sent! Check your email.",
"booking_embed_status_could_not_submit": "Could not submit",
"booking_embed_status_network_error": "Network error",
Expand All @@ -40,7 +40,7 @@
"booking_prefill_clear": "Not you? Clear",
"booking_agent_on_behalf_heading": "Booking on behalf of a client as {name}",
"booking_agent_on_behalf_body": "Enter your client's contact details below. They will be asked to confirm, and the company will settle the schedule and price.",
"booking_submitting": "Submitting...",
"booking_submitting": "Submitting…",
"booking_powered_by": "Powered by OpenInspection",
"booking_link_privacy_policy": "Privacy Policy",
"booking_link_terms": "Terms",
Expand Down
4 changes: 2 additions & 2 deletions messages/en/calendar.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
"calendar_all_day": "All day",
"calendar_block_edit_heading": "Edit blocked time",
"calendar_block_time": "Block time",
"calendar_block_saving": "Saving...",
"calendar_block_saving": "Saving…",
"calendar_block_save_changes": "Save changes",
"calendar_block_field_title": "Title",
"calendar_block_title_placeholder": "Personal appointment",
Expand Down Expand Up @@ -94,7 +94,7 @@
"find_a_time_inspector": "Inspector",
"find_a_time_anyone": "Anyone available",
"find_a_time_minutes": "{count} min",
"find_a_time_loading": "Checking availability...",
"find_a_time_loading": "Checking availability…",
"find_a_time_none": "No window that long is free on this day.",
"find_a_time_failed": "Availability could not be checked. Try again.",
"find_a_time_free_count": "{count} free",
Expand Down
Loading
Loading