Dev conversation/App changes
Valere CRM · Design foundations

Dev conversation

App changes

What would change in the app itself if the proposals on this site are accepted. Nothing on this page is implemented: each item is for the developers to discuss first.

The design pages show the app as it is. Where a page shows something the app does not have yet, it is marked there as proposed and listed here.

Last updated 2026-10-07. Usage counts are from web/packages/crm/src and web/shared/components on 2026-10-06.

Summary

#ChangeTouchesStatus
2Date picker instead of the browser's date controlA new shared component; 3 date fields in 3 filesProposed
4Six findings from documenting the design systemTokens, font loading, two duplicated constantsTo decide
6Password complexity and a strength barThree API inputs, validation.ts, three pages that set a passwordProposed
8A picture on the Not found pageOne file: NotFound.tsx, plus the picture as a componentProposed
9A picture in every empty, all-clear and error stateA shared EmptyState component and a sprite of 22 pictures; 37 places on 14 pages and page partsProposed

An item keeps its number for as long as it is discussed, and the number is not reused once the item is in the app. Item 1, the dropdown (branch feat/POC-UI-1-ui-improvements, ADR-0040), item 3, tabs that keep their width (branch fix/design-review-fixes, cherry-picked onto item 1's branch, where the concept's tabs, one weight for every tab, have since made the width fix unnecessary), and item 5, the split-screen sign-in page in the Live app layout (the same branch as item 1, ADR-0041), are in the app since 2026-10-07; the Select and Tabs sections of Components and the Sign-in layouts page now describe them as the app has them. Item 7, a logo and a favicon, is in the app since 2026-10-08 with the design concept’s mark, traced as a stand-in until the designer’s artwork; the Logo page shows it beside the proposals.

2. Date picker instead of the browser's date control

Raised on 2026-10-06 while reviewing the Text field page. The sample is live on the Date picker page.

Date picker

Proposed

What changes for the user

  • A date field shows the day as "12 Oct 2026", the format the rest of the app uses, instead of the browser's regional format.
  • Clicking it opens the app's own calendar: English month and weekday names, weeks starting on Monday, today ringed, the chosen day filled, Clear and Today.
  • Days that are not allowed, such as a future day for an Activity, are gray and cannot be chosen.

Why

The browser's date control follows the browser's language and region, not the app's. On a browser set to Ukrainian, the English app shows "12.10.2026" and a calendar with Ukrainian month names and buttons. Its look is the browser's too: square, black on white, unrelated to the app's controls.

In the app today

  • TextField with type="date" in three places: Due date in AddActionForm, Date in LogActivityForm (with max set to today), Tender due in ContractorRow.
  • The value travels as YYYY-MM-DD text through toDateInputValue and fromDateInputValue in pages/lead-detail/fields.tsx, which pin the day to local noon so a time zone cannot move it.

What it would touch

  • A new component in web/shared/components: a button and a calendar panel.
  • The three date fields switch to it; the Components gallery gets its samples.
  • The two date helpers stay as they are: the component takes and returns the same YYYY-MM-DD text.
  • Docs: the shared components list in docs/ARCHITECTURE.md, and an ADR.
  • No API, schema or data change.

To settle before starting

  1. Build it or bring a library. The dropdown (item 1, in the app) was built without one. A calendar library would be a new runtime dependency and needs approval.
  2. Typing a date. The browser's control lets a date be typed digit by digit. The sample is a button and a calendar only. Is choosing from the calendar enough for these three fields?
  3. Week start and wording. Monday first and "12 Oct 2026" are taken as right for a UK client. Confirm.
  4. Far dates. The sample steps one month at a time and has no jump to a year. Due dates are weeks or months away, so this may be enough.
  5. One panel behavior, two components. The app's dropdown (web/shared/components/Dropdown/, its useDropdown hook) already opens a panel under its control, fixed to the window, turns upward near the bottom, and closes on Escape, a press outside or a page scroll. Reuse that piece for the calendar, or write it again?
  6. Touch devices. The system's date picker suits a phone better. Mobile layout is out of scope for the PoC: ignore it, or keep the native control on touch?
  7. Screen readers. Each day is a button labeled with its full date ("Monday 12 October 2026"); it is not the ARIA grid pattern, and it has not been tested with a screen reader.
  8. Timing. Before the demo, or after?

How it would be verified

Web typecheck, lint and build, then a browser pass over Add action, Log activity (no future day can be chosen) and a contractor's Tender due, with the mouse and the keyboard, and once more on a browser set to another language.

4. Findings from documenting the design system

Places where the tokens, the design boards and the code disagree, noticed while writing the design pages. Nobody has asked for these changes: they are here so the developers can decide whether any is worth making.

#FindingIn the code todayOptionsStatus
4.1 The popover shadow is never used --shadow-popover is declared in tokens.css. The health breakdown, a floating panel, has shadow-card. The dropdown's list (item 1, in the app) is its first use. Give the breakdown shadow-popover, as the board intends; the date picker above would use it too. To decide
4.2 Three status tokens are unused good-mark, warn-mark, crit-mark: declared, in no class. Remove them, or keep them for a later use such as charts. To decide
4.3 A font is loaded and not used index.html loads IBM Plex Mono 400 and 500, and --font-mono is declared. No class uses font-mono. Stop loading it, or use it where the board does: dates in tables and record identifiers. To decide
4.4 Two text sizes are written as raw values text-[26px] leading-8 twice (tile figures) and text-[13px] leading-5 12 times (labels and table heads). The page titles are text-2xl since 2026-10-07. Name both in the @theme block, so each size lives in one place. To decide
4.5 One shadow is not a token The knob of the Land terms switch has Tailwind's default shadow. Leave it, or give it shadow-card. To decide
4.6 The table cell classes are defined twice CELL_HEAD and CELL are the same strings in pages/overview-parts.tsx and pages/account-detail/table.tsx. Keep one definition and import it. To decide

More detail on 4.1 to 4.5 is at the bottom of Colors, Typography and Spacing, radius, shadows.

6. Password complexity and a strength bar

Raised on 2026-10-06 with the new reset-password screens. Try it on the Reset password flow, screen "Set a new password · try it".

Password rules

Proposed

What changes for the user

  • A new password must have 8 to 72 characters, a number, an upper-case and a lower-case letter, and a symbol.
  • The rules are listed under the fields and turn green as they hold. A password sent with a rule open is refused, and the open rules turn red.
  • A strength bar under the new password reads Weak, Fair, Good or Strong. It only informs: the rules decide.

Why

Today any 8 bytes pass on a reset or a change of password, "aaaaaaaa" included. Staff accounts reach deal and contact data, so a guessable password is a real risk.

In the app today

  • ResetPasswordInput and ChangePasswordInput in api/src/auth/dto/: 8 to 72 bytes and no control characters (NO_CONTROL_CHARACTERS in api/src/common/password-rules.ts).
  • CreateStaffUserInput in api/src/staff-user/dto/: the same, and at least one number.
  • web/packages/crm/src/validation.ts mirrors them for the browser (MIN_PASSWORD_BYTES, MAX_PASSWORD_BYTES, hasNumber). RestorePassword.tsx lists two rules, CreateStaffUser.tsx three.

What it would touch

  • The three API inputs: the new rules as shared validators beside NO_CONTROL_CHARACTERS, one message per rule.
  • validation.ts: the same rules and the strength score, so the browser and the API agree.
  • RestorePassword.tsx, the password change on Account.tsx and CreateStaffUser.tsx: the rules list and the strength bar, as one shared component.
  • Tests for each rule on the three inputs. Docs: the password line of docs/DOMAIN_GLOSSARY.md.

To settle before starting

  1. Which rules. The design asks for all four kinds of character. Another common choice is three of the four, or length alone with a check against known leaked passwords. Valere may have its own policy.
  2. What counts as a symbol. The sample takes anything that is not a letter, a digit or a space, so "-" and "£" count.
  3. Existing passwords. The rules apply when a password is set, so passwords set before keep working. The six demo users share DEMO_PASSWORD; the default in both env templates already meets all five rules (checked on 2026-10-06).
  4. The score. The sample's bar counts the rules met and 12 characters or more. A library such as zxcvbn judges better but is a new runtime dependency and needs approval.
  5. Timing. Before the demo, or after?

How it would be verified

API unit tests for each rule on the three inputs, a real resetPassword and changePassword call with a weak and a strong password, then web typecheck, lint and build and a browser pass over the three pages.

8. A picture on the Not found page

Raised on 2026-10-07. Six illustrated versions are on the Not found page, after the page as the app draws it today. None is chosen.

Not found page

Proposed

What changes for the user

  • The Not found page leaves the layout: no sidebar, no top bar, signed in or not (decided 2026-10-07). The brand mark at the top left, the picture and the text centered, the two actions as the way back.
  • It gets a picture beside its text, one of the six, that moves a little. The text and the actions stay as they are; the title may change to fit the picture.
  • Signed out, the same page; the one action is Go to sign in.
  • On a narrow screen the text goes under the picture.

Why

A dead link is the one screen a demo audience reaches by accident, and today it is the plainest page in the app. A picture drawn from the business, a plan, a signpost, the stage bar, makes it read as part of the product rather than an error.

In the app today

  • NotFound.tsx (POC-6.1-404): text only, 560 px wide in the shell; the actions are links to the dashboard and to the record type's Overview, or to Sign in when signed out.
  • SessionLayout.tsx picks the shell or the auth layout; App.tsx routes * to it.
  • The "Error 404" line uses text-gray-600, a Tailwind default rather than one of the Design Foundations grays; the samples use gray-500.

What it would touch

  • App.tsx: the * route leaves SessionLayout for a bare layout with the brand mark, like AuthLayout without the card; SessionLayout.tsx then has no user and can go.
  • A picture component beside NotFound.tsx: inline SVG with its CSS animation, no image file, no library.
  • NotFound.tsx: the two-column layout and the narrow-screen fold; the title; the actions still read the session.
  • Docs: the crm row of docs/ARCHITECTURE.md.
  • No API, schema or data change.

To settle before starting

  1. Which picture. Empty plot and Signpost say land; Not a stage and Lost record say how the product works; Fog and Crane are the lightest in tone. One for both the signed-in and the signed-out page.
  2. The title. Keep the app's "Page not found" under every picture, or let the title follow it ("This plot is not on the plan").
  3. The signpost's arms as links. In that version the arms could replace the two buttons. The buttons are what a screen reader and a keyboard reach; keep them.
  4. Reduced motion. The samples hold their last frame. Keep that.
  5. Timing. Before the demo, or after? A dead link is likely in a demo.

How it would be verified

Web typecheck, lint and build, then a browser pass: a dead address signed in and signed out, a removed record's address, at a laptop width and a phone width, with reduced motion on and off. The two actions must lead where they lead today.

9. A picture in every empty, all-clear and error state

Raised on 2026-10-07. Every state is on the Illustrations page, where it sits, with the sentence the app shows today.

Illustrations

Proposed

What changes for the user

  • An empty list, an empty panel on a record, a dashboard card with nothing to flag, a page that could not load and the auth card's notices get a small still picture above their sentence. The sentence does not change.
  • Under some sentences a line says where the first row comes from, naming the button: "The first one comes in through New lead."
  • Two states that have no sentence today get one: the Morning Meeting Board with nothing on it, and Sign in after the session ended ("You were signed out.").
  • Loading, inline facts, locked fields and form bars stay as they are.

Why

A fresh system and a filtered list are mostly empty states, and today every one of them is one gray sentence. A picture tells "nothing yet" from "nothing matches" from "nothing wrong" at a glance, and the all-clear states on the dashboards deserve to look like good news.

In the app today

  • Each list page writes its own empty row (LeadsOverview.tsx and the other five); each panel its own gray sentence (BuyersTab.tsx, LegalsTab.tsx, ActivityTimeline.tsx, FollowUpTracker.tsx, the account and contact panels); the dashboard cards pass empty="…" to CardBody (cards.tsx, executive-cards.tsx, role-cards.tsx).
  • A page that could not load shows FormError at the top of an empty page; so does /staff/new for a non-admin.
  • A failed token refresh signs out and opens Sign in with no message (apollo-client.ts).

What it would touch

  • An EmptyState component in web/shared/components: picture, sentence, optional line and button, in two sizes; a sprite of the 22 pictures as inline SVG symbols, no image files.
  • A PageError for the pages that could not load, in place of the bar; SessionLayout or Sign in for the signed-out notice, with the reason carried in the sign-out.
  • The 37 places listed on the page: each one line, the sentence moved into the component.
  • Docs: docs/ARCHITECTURE.md (the web/shared row); the Components gallery.
  • No API, schema or data change.

To settle before starting

  1. All of them, or the lists first. The six lists and the page error are the ones every user meets; the record panels and the dashboard cards can follow.
  2. The added lines. "The first one comes in through New lead" names a button that exists on this branch (POC-6.0). Agree the wording, or leave the lines out and keep only the picture.
  3. The signed-out notice. It needs the sign-out to say why it happened. A query string on the Sign in address, or a flag in session storage?
  4. The board's sentence. "Nothing on the board today." is new; the board has no sentence now.
  5. Timing. The demo data fills nearly every state, so the audience sees few of these; after the demo is fine.

How it would be verified

Web typecheck, lint and build, then a browser pass with an empty database and with the demo data: each list with and without a filter, each record panel, both dashboards, the board, a page with the API stopped, /staff/new as a Valere role, and Sign in after an expired session.

How an item moves