-
+
+
+
}
diff --git a/apps/sim/auth-migration/baseline.json b/apps/sim/auth-migration/baseline.json
new file mode 100644
index 00000000000..f606519dec0
--- /dev/null
+++ b/apps/sim/auth-migration/baseline.json
@@ -0,0 +1,254 @@
+{
+ "base": "9c571fbcea10ed2001ea81fc967830e33dadcbf4",
+ "routes": [
+ {
+ "file": "apps/sim/app/(auth)/login/page.tsx",
+ "url": "/login",
+ "entry": "page.tsx"
+ },
+ {
+ "file": "apps/sim/app/(auth)/oauth/consent/page.tsx",
+ "url": "/oauth/consent",
+ "entry": "page.tsx"
+ },
+ {
+ "file": "apps/sim/app/(auth)/oauth/sign-in/route.ts",
+ "url": "/oauth/sign-in",
+ "entry": "route.ts"
+ },
+ {
+ "file": "apps/sim/app/(auth)/reset-password/page.tsx",
+ "url": "/reset-password",
+ "entry": "page.tsx"
+ },
+ {
+ "file": "apps/sim/app/(auth)/signup/page.tsx",
+ "url": "/signup",
+ "entry": "page.tsx"
+ },
+ {
+ "file": "apps/sim/app/(auth)/sso/launch/[providerId]/route.ts",
+ "url": "/sso/launch/[providerId]",
+ "entry": "route.ts"
+ },
+ {
+ "file": "apps/sim/app/(auth)/sso/page.tsx",
+ "url": "/sso",
+ "entry": "page.tsx"
+ },
+ {
+ "file": "apps/sim/app/(auth)/verify/page.tsx",
+ "url": "/verify",
+ "entry": "page.tsx"
+ },
+ {
+ "file": "apps/sim/app/(interfaces)/chat/[identifier]/page.tsx",
+ "url": "/chat/[identifier]",
+ "entry": "page.tsx"
+ },
+ {
+ "file": "apps/sim/app/cli/auth/done/page.tsx",
+ "url": "/cli/auth/done",
+ "entry": "page.tsx"
+ },
+ {
+ "file": "apps/sim/app/cli/auth/page.tsx",
+ "url": "/cli/auth",
+ "entry": "page.tsx"
+ },
+ {
+ "file": "apps/sim/app/credential-groups/complete/page.tsx",
+ "url": "/credential-groups/complete",
+ "entry": "page.tsx"
+ },
+ {
+ "file": "apps/sim/app/credential-groups/enroll/[token]/page.tsx",
+ "url": "/credential-groups/enroll/[token]",
+ "entry": "page.tsx"
+ },
+ {
+ "file": "apps/sim/app/credential-groups/slack-complete/page.tsx",
+ "url": "/credential-groups/slack-complete",
+ "entry": "page.tsx"
+ },
+ {
+ "file": "apps/sim/app/desktop/auth/page.tsx",
+ "url": "/desktop/auth",
+ "entry": "page.tsx"
+ },
+ {
+ "file": "apps/sim/app/desktop/connect/complete/page.tsx",
+ "url": "/desktop/connect/complete",
+ "entry": "page.tsx"
+ },
+ {
+ "file": "apps/sim/app/desktop/connect/page.tsx",
+ "url": "/desktop/connect",
+ "entry": "page.tsx"
+ },
+ {
+ "file": "apps/sim/app/desktop/done/page.tsx",
+ "url": "/desktop/done",
+ "entry": "page.tsx"
+ },
+ {
+ "file": "apps/sim/app/f/[token]/page.tsx",
+ "url": "/f/[token]",
+ "entry": "page.tsx"
+ },
+ {
+ "file": "apps/sim/app/invite/[id]/page.tsx",
+ "url": "/invite/[id]",
+ "entry": "page.tsx"
+ },
+ {
+ "file": "apps/sim/app/knowledge/github/setup/page.tsx",
+ "url": "/knowledge/github/setup",
+ "entry": "page.tsx"
+ },
+ {
+ "file": "apps/sim/app/oauth-error/page.tsx",
+ "url": "/oauth-error",
+ "entry": "page.tsx"
+ },
+ {
+ "file": "apps/sim/app/oauth/chat-complete/page.tsx",
+ "url": "/oauth/chat-complete",
+ "entry": "page.tsx"
+ },
+ {
+ "file": "apps/sim/app/oauth/credential-connected/page.tsx",
+ "url": "/oauth/credential-connected",
+ "entry": "page.tsx"
+ },
+ {
+ "file": "apps/sim/app/slack-search/connect/[token]/page.tsx",
+ "url": "/slack-search/connect/[token]",
+ "entry": "page.tsx"
+ },
+ {
+ "file": "apps/sim/app/slack-search/install/[teamId]/page.tsx",
+ "url": "/slack-search/install/[teamId]",
+ "entry": "page.tsx"
+ }
+ ],
+ "files": {
+ "apps/sim/app/oauth-error/page.tsx": "1a5fa4e8748cbf416060acb0873f2e4d0ff1a7660d387d1f3ba264ab9ccbe7bd",
+ "apps/sim/app/(auth)/auth-redirect.ts": "2066538d0c022ac73868865ea27099f372b95069fe0bed62cc9d46f4c9f6a009",
+ "apps/sim/app/(auth)/layout.tsx": "dcaf0830bae5f5505f427cd34498cfcb89b05c35b8b55c227d3ce2395674db75",
+ "apps/sim/app/oauth/credential-connected/page.tsx": "e7ec5197c99bbc101bbe506b0fe3d5d2dd69701e571932a1a073474123daf114",
+ "apps/sim/app/oauth/chat-complete/chat-complete-handoff.tsx": "0283caf4e4cd9be7e2457c4bba479e98415c10c62f8ce697d2bcb19c7624288e",
+ "apps/sim/app/oauth/chat-complete/page.tsx": "edb31812ddc4a5609bfa43016c1e432869950e2030e1404166f0db07404e66f7",
+ "apps/sim/app/(interfaces)/chat/constants.ts": "487236115f9c4747dfc209a529729c149a868833b1701fd81bd209b5169076a1",
+ "apps/sim/app/(interfaces)/chat/[identifier]/loading.tsx": "45c3bd015b1291da7cb3d2022eeb4d581796d06eb92bd8d576c4152f289fad06",
+ "apps/sim/app/(interfaces)/chat/[identifier]/chat.tsx": "13406e032408585d436a7269ea2a48f48a3d22bc3ec8b198d0d193abcf26cd77",
+ "apps/sim/app/(interfaces)/chat/[identifier]/office-embed-init.tsx": "a1d7ecb4eff0c967ad823a266cd2cebab1b347a79bbeba7b5063a2d5fe01f144",
+ "apps/sim/app/(interfaces)/chat/[identifier]/page.tsx": "d638860ed9225fdda9a8d8306604dbaed81cc0ec07dcee9213c4cb4011290762",
+ "apps/sim/app/(interfaces)/chat/components/index.ts": "b7d60efb8122e1429d1ab3baca379d9f6affce9df69521ae21212ddbe30233f2",
+ "apps/sim/app/(interfaces)/chat/hooks/use-chat-streaming.ts": "6394629fb8e17e9f8f72ae377687b56f5bea945498781b31cc536641200e1d4a",
+ "apps/sim/app/(interfaces)/chat/hooks/index.ts": "32c8d5c582c99e5546fe7e2f65d3307ced18427e5762ad5647ca26ebd1427375",
+ "apps/sim/app/(interfaces)/chat/components/error-state/error-state.tsx": "9c1bcd1888e02c38e261961d6b2a11a6b101a1755fb52e2373ced85a75a63a0e",
+ "apps/sim/app/(interfaces)/chat/components/input/public-chat-action-button.tsx": "ec3739499c3bed580b3215928bc697066710f61bce07a16de212001df39c4c70",
+ "apps/sim/app/(interfaces)/chat/components/input/input.tsx": "3fef462f1d50088960a3c15052492f3adcde404739d84b13d83e4643a2a98c3c",
+ "apps/sim/app/(interfaces)/chat/components/message/message.tsx": "9850c64457e992a221c98cb0517e9a10e6909f9d46312a2473503b4b3c422cad",
+ "apps/sim/app/(interfaces)/chat/components/loading-state/loading-state.tsx": "cec6ab662cd3ea6ecea214ff080e40d5bc88df7e37b6ed8628f5f53de048a0f3",
+ "apps/sim/app/(interfaces)/chat/components/message-container/message-container.tsx": "f03a0a0717f3dfa2735c7238c3c13ff76cd4cd767488e707da16b1a657431cb7",
+ "apps/sim/app/(interfaces)/chat/components/header/header.tsx": "f1ef8593a60f5dc37dded4fc3527a42854c164bab45b663a2ac289ac4ce04ff1",
+ "apps/sim/app/(interfaces)/chat/components/auth/password/password-auth.tsx": "83e1570c272817e4b770cbc4fffa55c2d48e6e1e0952627bf2fee81bae872222",
+ "apps/sim/app/(interfaces)/chat/components/auth/email/email-auth.tsx": "4b58d5fb519829368c026489d563357428cd2bf369b909a4c700839d0c3a7a54",
+ "apps/sim/app/(interfaces)/chat/components/message/components/markdown-renderer.tsx": "6407200c17ea871832fa9ffba60509504afcfece11b8fc9902e1e35f1aacf6dc",
+ "apps/sim/app/(interfaces)/chat/components/message/components/file-download.tsx": "f0bba0f3e2e3c06d8c3f35c754652f6dfa318e598ae088884d475e184b8c0716",
+ "apps/sim/app/slack-search/install/[teamId]/page.tsx": "96999ba5e81fa49b5e7502c4c4a4f777e270d324a79e144528b71ad0bd6c7a19",
+ "apps/sim/app/slack-search/connect/[token]/slack-search-onboarding.tsx": "0ce711a67dec76cb515c13ee2837388d35d3d7765c24c94f26dc8d38798bceaf",
+ "apps/sim/app/slack-search/connect/[token]/page.tsx": "99e6bfc7731eb33eb4fed5be67702937f01571a4af14f315aa3a310d3116e024",
+ "apps/sim/app/desktop/auth/validation.ts": "db171a6a1e4067a2d43b33fab60ece29b9d8a17d5def63f75cbfb6dddbbc77b1",
+ "apps/sim/app/desktop/auth/authorize-handoff.tsx": "c4b756f1e144df8021d00160daa218ef0bb89fb57d4c8f34b57078b407c6d935",
+ "apps/sim/app/desktop/auth/page.tsx": "1070f9218a544d6d0e3071da1669d3d948482a4612ca5fbd56c185537180d0f2",
+ "apps/sim/app/desktop/done/page.tsx": "5b522f3e0c17c26775a154a4e64b3d13b47aa689e4964d5779fa2fe6b46608f0",
+ "apps/sim/app/desktop/connect/validation.ts": "4354fe3d629bddc11e3b9532d0b77064a430dc19d50570caf0af7023bc89f635",
+ "apps/sim/app/desktop/connect/source-connect-launcher.tsx": "6a1df65d848ac6bf789237f9c6402ca8e2c325a4c4df504865d0a51be0701244",
+ "apps/sim/app/desktop/connect/source-completion.tsx": "c04b9971ed3cbc4ac629b68b63052cbf37d51d2c5432efe2a49d6c412eac39d0",
+ "apps/sim/app/desktop/connect/switch-account.tsx": "3bb07d508a5249038b83b9769f84af30bbcddb3162f301703a59e4f3c49c19cc",
+ "apps/sim/app/desktop/connect/page.tsx": "207969389b8ba5ac280e83eefa3ac37790039f8f58e207ea76909b6913a7afc3",
+ "apps/sim/app/desktop/connect/connect-launcher.tsx": "44b5acff28880b2c33cd3ddca8a00b7ddfb56c1f91029bd7ff9683e9d867d267",
+ "apps/sim/app/desktop/components/desktop-handoff-shell/desktop-handoff-shell.tsx": "c815106b3dd496ab8b0dc592da143f5420f3599eb762bc155b10f750a242fa17",
+ "apps/sim/app/desktop/components/desktop-handoff-shell/index.ts": "a96be859afb28fa3b1ce526deb28ecd7fd6ade07a798dfbb4ba9913446e90e4d",
+ "apps/sim/app/desktop/connect/complete/page.tsx": "5d8b8c144aa52d943595c447a4e2960a6b255457ab6a988951037e23865b574c",
+ "apps/sim/app/cli/auth/search-params.ts": "2566cfdd3422e28b5db036f3dfb0088678537c4668caac627af34f0a574ed59d",
+ "apps/sim/app/cli/auth/cli-auth-request.ts": "38a8749efd7748e9d2d5693522b15a0758282da1272aee5b9f245e93e4ecca74",
+ "apps/sim/app/cli/auth/cli-auth-view.tsx": "83e3243cab31de927928e9bb9a93775035c5950db0819fabd11c02f6ab6ab89d",
+ "apps/sim/app/cli/auth/loading.tsx": "5c7911bfe083244640dbac30d697d63c60f630e1c1d110cef988c562c5b220a9",
+ "apps/sim/app/cli/auth/page.tsx": "bdf0b30e9dcff7539d4e1b27b9c0a399fe1103da23ad85244ccf5b8187d8b090",
+ "apps/sim/app/cli/auth/done/search-params.ts": "c4a6671548e58162081b632c9d78c3a62f4d110b66e63b12e70c16209c4664fc",
+ "apps/sim/app/cli/auth/done/page.tsx": "8a8b713f29217004245a77f180796d4f3e77cf97e1ade9ec3b27b712071c6ea1",
+ "apps/sim/app/cli/auth/done/cli-auth-done-view.tsx": "d2662ec83b79096b5d4e37e93e67e5cfc098f531198d45751b8cca93ee9505a9",
+ "apps/sim/app/f/[token]/public-file-sso-auth.tsx": "1164e4fb43bc59128c8699d1584724c28fbe3815e811cf9ce7e5352022f57ac0",
+ "apps/sim/app/f/[token]/opengraph-image.tsx": "69cdebce026fd8990d5254269bdac67f232086e0dc0d9e81739ee31e12543475",
+ "apps/sim/app/f/[token]/utils.ts": "6f7c5031ef57897b40853ff7bd8262891af683af907fa18d71c2f022e53d42a5",
+ "apps/sim/app/f/[token]/public-file-view.tsx": "9ecc7163d4cf50aebd9d59fda89d5b7b2e1376f09cee6462662b9eb4f04c7f80",
+ "apps/sim/app/f/[token]/public-file-email-auth.tsx": "379223ecfd034371eeae2da3069ea98c83283c6f238f853d78686b13184878b5",
+ "apps/sim/app/f/[token]/page.tsx": "07fcb3522344413ef0a0bd5fde977901294415ec3e0cb90287ff6b3df0cb696a",
+ "apps/sim/app/f/[token]/public-file-auth-shell.tsx": "2820fc9902729473a88186caa3b5b556ead07cb367a769c328972ba929eae33c",
+ "apps/sim/app/f/[token]/public-file-auth.tsx": "9b406e4938cc7ba1bfbca4504b45b4238445fe1bf4371038744904a7f27970aa",
+ "apps/sim/app/invite/components/invitation-workspace-access.tsx": "875d28aa2761b3c9a40193f55bd4fca7ca2e7f8986f8d3675a79c0a1cfe8a92b",
+ "apps/sim/app/invite/components/invitation-disclosure.tsx": "800e58ce7872bfc41b95d8a67904ada2a3a0d1bef40fdf4a4053576bae902953",
+ "apps/sim/app/invite/components/status-card.tsx": "2df446ebf42162e3f221709bcd6909854b5346515c7016a3ffcffe90f6df88f3",
+ "apps/sim/app/invite/components/layout.tsx": "e74dfa769b8a4415032ff99610a4847409c6545d781ddb11f9a58b8a79be6341",
+ "apps/sim/app/invite/components/index.ts": "f0a304ce772d771da821f18e6999db589d537aa06f7d6d75e613050d0cc935a3",
+ "apps/sim/app/invite/[id]/invite.tsx": "eabd903e206e44664988d5620ec3c53eb8b280f3ebbf1021579c508a2d2cfbca",
+ "apps/sim/app/invite/[id]/loading.tsx": "22f10935589df6c151f790f2eba1414537be4d34c66b8d896bcb7074c5a7ded1",
+ "apps/sim/app/invite/[id]/page.tsx": "db659e839b5f8aa5b5e649cbdebc4e88dc726e62ca913fe57e5fee5b2ba5fe23",
+ "apps/sim/app/knowledge/github/setup/setup.tsx": "f41741ce6c1d0d89ca13d89502415174212c046b2c0bdf4a8cf5d8bf9ec66a47",
+ "apps/sim/app/knowledge/github/setup/page.tsx": "d68fd20792fc06311568b1820643ffcce16eae31ac68510af8d926768b8e4b70",
+ "apps/sim/app/credential-groups/slack-complete/slack-completion.tsx": "86d25bd0ecfa893b1b621ee8637405bd48222711132a88e4da1cc0e0a5164115",
+ "apps/sim/app/credential-groups/slack-complete/page.tsx": "293ddb3def11db2d97f08bbb1431e0d31aa52360b2382e72ce09cd26cbed500c",
+ "apps/sim/app/credential-groups/complete/completion-handoff.tsx": "544edefe79cf7b3dbb966f9ba4aeb2911b8c4dd2b4f363738caa3897197cd907",
+ "apps/sim/app/credential-groups/complete/page.tsx": "345c7d36b1513df311c00a9e91d0034ec446d63bbea9cce0dd51d1a501880f5f",
+ "apps/sim/app/credential-groups/enroll/[token]/search-params.ts": "69addc3f9a90c9bb4da58ef468ebf1e39b16187508ac922441dcaddd22f0742f",
+ "apps/sim/app/credential-groups/enroll/[token]/oauth-reconnect-link.tsx": "7fadd3a5542b1308dadeed29e27d4959d0d42c5dc37f6de37d6e04cb4226a7ce",
+ "apps/sim/app/credential-groups/enroll/[token]/oauth-toast.tsx": "4ea38497e5bbd6c4eac152573f37c0b02e7b6a355636b97b7543e9b65afd18ac",
+ "apps/sim/app/credential-groups/enroll/[token]/page.tsx": "6a313f7a7e2fcf75c543fd62098f32575b6f57458641f2740bfb8f54ce6d665b",
+ "apps/sim/app/(auth)/verify/use-verification.ts": "92a1ea22661f1268c2ee826728cd3979082d6a16016f83d70a6b9a209ead91ef",
+ "apps/sim/app/(auth)/verify/verify-content.tsx": "57c2534e8dcc1faf0b0c8a0c976f0ebecfeef60840ac986bf8d0cc6b25cb9529",
+ "apps/sim/app/(auth)/verify/loading.tsx": "a3f80de1f6a97ecd94a9b090edeb71c807bea9a3b53103d91737ff2fecdfeda2",
+ "apps/sim/app/(auth)/verify/page.tsx": "b0a53fc84f03a8d3fc06b4b396ea726fb57070c0017642abaece747453050c4a",
+ "apps/sim/app/(auth)/signup/search-params.ts": "824ab478bb22311eb98a8edc384826498f42f2c98f3ecdd3af01a7edeb16cdc0",
+ "apps/sim/app/(auth)/signup/registration-disabled.tsx": "4dd7cf7640fa6eb6ef376c01e346208e2265cfa7405e905712c203eaf33d792d",
+ "apps/sim/app/(auth)/signup/loading.tsx": "c50b6797c33ec5b9ddc33775f6dca1271634211e2727e18d4b7da6a204790ef0",
+ "apps/sim/app/(auth)/signup/signup-form.tsx": "0f367f32f12af869fbb754646146922698a9c2b8cd09ae52520e0e488fd31603",
+ "apps/sim/app/(auth)/signup/page.tsx": "d55744d1f9341e76a2c94e6e0ea81268e4000de5e16d19b2d431f2a17801f042",
+ "apps/sim/app/(auth)/components/social-login-buttons.tsx": "f5367bc0f43d264e761102391d7e8545c6d06e7d050443013e28df68d359d97e",
+ "apps/sim/app/(auth)/components/auth-field.tsx": "302a8fb1a7336e2c868ffeed7c9e357649915d13eeee5b4d1d28a3b38ae45457",
+ "apps/sim/app/(auth)/components/auth-text-link.tsx": "a89b88257e64633c1c401c1deda9fe24926fea44de1a173a4da62f29ad88a50c",
+ "apps/sim/app/(auth)/components/auth-shell.tsx": "21b7e9eb214754a187c1aefc378ab8451684e46b73265a7fa376b8e8225f012b",
+ "apps/sim/app/(auth)/components/auth-legal-footer.tsx": "6214e8fc00177b8f47833ea5a1d06204a936579a53adc0f3e025d39c0a94fe05",
+ "apps/sim/app/(auth)/components/auth-nav-prompt.tsx": "037772a5a5017e18dec6665ffd2476341055cb0e6266eca367bd945f784c02c0",
+ "apps/sim/app/(auth)/components/auth-submit-button.tsx": "16f535ecfcb888296947bc11dad6ca00ad67897011c53c83deb85e4db51e23bd",
+ "apps/sim/app/(auth)/components/auth-loading-skeleton.tsx": "a42e64528f81aee40f56da4571f871682cb403eebbea2044963b9f610a7a9c47",
+ "apps/sim/app/(auth)/components/oauth-provider-checker.tsx": "ea5ba223887cbf90e9175e5d7e59a1d23b8f16804cb99d0802f7da54ed503dea",
+ "apps/sim/app/(auth)/components/auth-divider.tsx": "384918b4f425581f47833198c2fc532467715192303dd0676064ab8090895949",
+ "apps/sim/app/(auth)/components/password-input.tsx": "8e86414021d3eaa1d5bacce8fdd5ed49a1268bd6784c3428774e6012e892e336",
+ "apps/sim/app/(auth)/components/constants.ts": "64a9bf8f3fdb8fce5e96f5def9939a1f520a842f02d23dd111948f0c89a4c3a3",
+ "apps/sim/app/(auth)/components/auth-input.tsx": "12a175523d140b15a8eef45ee3ab709f64b8603ac4f2bed264dfb5b69af06209",
+ "apps/sim/app/(auth)/components/auth-header.tsx": "39bb7c86bb70d07adb3bd8e44ff2b7a0c25151b11c2993b23bbb49b61583d16d",
+ "apps/sim/app/(auth)/components/sso-login-button.tsx": "c06c62a419af92d9c3c3eb624e927447e636e9b8ffb1c2e30451c20ec299ece0",
+ "apps/sim/app/(auth)/components/index.ts": "f0d2af59924b323a846d2fbb16dbf8367c4bae1e91afb8e40d4d39ea8d185bd2",
+ "apps/sim/app/(auth)/components/auth-form-message.tsx": "19688a09af97fd41113157486de4b03835f01217f9ab9b611ddd9284f8e9a5f1",
+ "apps/sim/app/(auth)/components/support-footer.tsx": "5122b11949594d2812eb92f20f8d18cda90b214bcebe8ac745e7448d660550da",
+ "apps/sim/app/(auth)/sso/loading.tsx": "cebab4d6de272d4d4b055ee0091453a71d7158a37124bf2053434593f58fb94d",
+ "apps/sim/app/(auth)/sso/page.tsx": "b917cd70a259229f1d0a3cc8346d21f673810f245467cf3aafe5121d7707fd1e",
+ "apps/sim/app/(auth)/reset-password/reset-password-form.tsx": "74f0b8aba88b70342be18bd2ee64f53a229c14755569392e2250f0b59fa1e8f5",
+ "apps/sim/app/(auth)/reset-password/reset-password-content.tsx": "fbd1b53ea8c4a5c86c8a9a3d1ced414feafbb8a756be428d24d9f34252a61fca",
+ "apps/sim/app/(auth)/reset-password/loading.tsx": "33475f75b4f3cae9f53b4482b119c53fcfb8c899b437c8468411aed28380fc66",
+ "apps/sim/app/(auth)/reset-password/page.tsx": "b9dd840603e49ed1926a0944d04a3bf2e2cb30656f817587689250503b4546e8",
+ "apps/sim/app/(auth)/login/loading.tsx": "be903941ac9953b609b268e7d63b6506004eafb10e6ebdcb3ba767fa3c186dff",
+ "apps/sim/app/(auth)/login/login-form.tsx": "f6294c9a6401ca8c64b2d1211ebaef5923aeefa6689712cc60ac470d303b567c",
+ "apps/sim/app/(auth)/login/page.tsx": "265df71867021458275fe5dad2e58f66ea81cf8b5c8ba98caf5c73fc9039ee5e",
+ "apps/sim/app/(auth)/oauth/consent/search-params.ts": "febb15167ebda615cc0bb54c0b2c587921eb4ea52c0198313952451659a8e74f",
+ "apps/sim/app/(auth)/oauth/consent/loading.tsx": "8794dcadbbccf1b05943a22d1aea22190a7520c82f1ad08f1e4747ae410da066",
+ "apps/sim/app/(auth)/oauth/consent/page.tsx": "3fd5d9ad77386745df773a251f5f4d0da02224b8f5a59f92e103a9ec7e51f8cb",
+ "apps/sim/app/(auth)/oauth/consent/consent-view.tsx": "4a3b7dee52d87038de070890226a9ad6fdd81158c879cbbe41ee560d0ae7caea",
+ "apps/sim/app/(auth)/oauth/sign-in/route.ts": "d6b46c7f0cd4fe54ab90dfbc786d6a5fe165c5a5b48d5dedb17e119fc2569a22",
+ "apps/sim/app/(auth)/sso/launch/[providerId]/route.ts": "7737458be1a0e0a10df8e3859441f37d8953c3cc256c8ed83f846d2cf4ee6301"
+ }
+}
diff --git a/apps/sim/auth-migration/coverage.json b/apps/sim/auth-migration/coverage.json
new file mode 100644
index 00000000000..1d7e4346089
--- /dev/null
+++ b/apps/sim/auth-migration/coverage.json
@@ -0,0 +1,322 @@
+{
+ "scope": "Auth presentation only; existing authentication handlers, resource authorization, cookies, redirects and external providers remain authoritative.",
+ "routes": [
+ {
+ "file": "apps/sim/app/(auth)/login/page.tsx",
+ "url": "/login",
+ "entry": "page.tsx",
+ "states": [
+ "email-password",
+ "recovery",
+ "invalid credentials",
+ "policy refusal",
+ "pending",
+ "email disabled",
+ "SSO only",
+ "no methods"
+ ],
+ "actions": [
+ "credential submit",
+ "forgot password",
+ "recovery back",
+ "signup cross-link",
+ "provider sign-in"
+ ]
+ },
+ {
+ "file": "apps/sim/app/(auth)/oauth/consent/page.tsx",
+ "url": "/oauth/consent",
+ "entry": "page.tsx",
+ "states": [
+ "loading",
+ "trusted client",
+ "missing/unsigned/tampered/expired refusal",
+ "allow pending",
+ "deny pending",
+ "switch pending",
+ "error"
+ ],
+ "actions": ["allow", "deny", "use another account"]
+ },
+ {
+ "file": "apps/sim/app/(auth)/oauth/sign-in/route.ts",
+ "url": "/oauth/sign-in",
+ "entry": "route.ts",
+ "states": ["signed OAuth request redirect"],
+ "actions": []
+ },
+ {
+ "file": "apps/sim/app/(auth)/reset-password/page.tsx",
+ "url": "/reset-password",
+ "entry": "page.tsx",
+ "states": [
+ "missing token",
+ "invalid token",
+ "password policy",
+ "confirmation mismatch",
+ "pending",
+ "success"
+ ],
+ "actions": ["reset password", "back to login"]
+ },
+ {
+ "file": "apps/sim/app/(auth)/signup/page.tsx",
+ "url": "/signup",
+ "entry": "page.tsx",
+ "states": [
+ "default",
+ "all validation messages",
+ "pending",
+ "registration disabled",
+ "invite-only",
+ "email disabled"
+ ],
+ "actions": ["create account", "signin cross-link", "provider sign-in"]
+ },
+ {
+ "file": "apps/sim/app/(auth)/sso/launch/[providerId]/route.ts",
+ "url": "/sso/launch/[providerId]",
+ "entry": "route.ts",
+ "states": ["SSO provider redirect"],
+ "actions": []
+ },
+ {
+ "file": "apps/sim/app/(auth)/sso/page.tsx",
+ "url": "/sso",
+ "entry": "page.tsx",
+ "states": [
+ "idle",
+ "invalid email",
+ "provider discovery",
+ "provider missing",
+ "expired test link",
+ "pending",
+ "provider failure"
+ ],
+ "actions": ["discover provider", "SSO redirect", "login back link"]
+ },
+ {
+ "file": "apps/sim/app/(auth)/verify/page.tsx",
+ "url": "/verify",
+ "entry": "page.tsx",
+ "states": [
+ "idle",
+ "auto-submit",
+ "verifying",
+ "invalid code",
+ "resend cooldown",
+ "resend failure",
+ "verified",
+ "email unavailable"
+ ],
+ "actions": ["verify email", "resend code", "back to signup"]
+ },
+ {
+ "file": "apps/sim/app/(interfaces)/chat/[identifier]/page.tsx",
+ "url": "/chat/[identifier]",
+ "entry": "page.tsx",
+ "states": [
+ "password gate",
+ "email entry",
+ "OTP",
+ "resend cooldown",
+ "email change",
+ "SSO",
+ "unavailable",
+ "unlocked chat"
+ ],
+ "actions": ["unlock chat", "send/verify/resend code", "change email", "SSO"]
+ },
+ {
+ "file": "apps/sim/app/cli/auth/done/page.tsx",
+ "url": "/cli/auth/done",
+ "entry": "page.tsx",
+ "states": ["complete", "cancelled", "invalid outcome"],
+ "actions": []
+ },
+ {
+ "file": "apps/sim/app/cli/auth/page.tsx",
+ "url": "/cli/auth",
+ "entry": "page.tsx",
+ "states": [
+ "invalid request",
+ "signed out redirect",
+ "platform and runtime scopes",
+ "workspace loading",
+ "workspace failure",
+ "no default workspace",
+ "pending",
+ "approval failure"
+ ],
+ "actions": ["copy pairing code", "select workspace", "connect"]
+ },
+ {
+ "file": "apps/sim/app/credential-groups/complete/page.tsx",
+ "url": "/credential-groups/complete",
+ "entry": "page.tsx",
+ "states": ["complete"],
+ "actions": []
+ },
+ {
+ "file": "apps/sim/app/credential-groups/enroll/[token]/page.tsx",
+ "url": "/credential-groups/enroll/[token]",
+ "entry": "page.tsx",
+ "states": [
+ "full enrollment",
+ "focused reconnect",
+ "all connection identities",
+ "managed connectors",
+ "expired/unavailable",
+ "provider failure",
+ "submitted"
+ ],
+ "actions": ["connect", "reconnect", "submit", "return to accounts/search"]
+ },
+ {
+ "file": "apps/sim/app/credential-groups/slack-complete/page.tsx",
+ "url": "/credential-groups/slack-complete",
+ "entry": "page.tsx",
+ "states": ["complete", "failure"],
+ "actions": []
+ },
+ {
+ "file": "apps/sim/app/desktop/auth/page.tsx",
+ "url": "/desktop/auth",
+ "entry": "page.tsx",
+ "states": ["signed out redirect", "approval", "pending", "refused", "failure", "handoff"],
+ "actions": ["continue to desktop", "retry"]
+ },
+ {
+ "file": "apps/sim/app/desktop/connect/complete/page.tsx",
+ "url": "/desktop/connect/complete",
+ "entry": "page.tsx",
+ "states": ["complete", "failure"],
+ "actions": []
+ },
+ {
+ "file": "apps/sim/app/desktop/connect/page.tsx",
+ "url": "/desktop/connect",
+ "entry": "page.tsx",
+ "states": [
+ "browser waiting",
+ "authenticated launch",
+ "source connect",
+ "account switch",
+ "retry"
+ ],
+ "actions": ["open browser", "retry", "switch account"]
+ },
+ {
+ "file": "apps/sim/app/desktop/done/page.tsx",
+ "url": "/desktop/done",
+ "entry": "page.tsx",
+ "states": ["complete"],
+ "actions": []
+ },
+ {
+ "file": "apps/sim/app/f/[token]/page.tsx",
+ "url": "/f/[token]",
+ "entry": "page.tsx",
+ "states": [
+ "password gate",
+ "email entry",
+ "OTP",
+ "resend cooldown",
+ "email change",
+ "SSO",
+ "failure",
+ "unlocked viewer"
+ ],
+ "actions": ["unlock resource", "send/verify/resend code", "change email", "SSO"]
+ },
+ {
+ "file": "apps/sim/app/invite/[id]/page.tsx",
+ "url": "/invite/[id]",
+ "entry": "page.tsx",
+ "states": [
+ "signed out",
+ "registration disabled",
+ "loading",
+ "expired",
+ "wrong account",
+ "accept pending",
+ "accept failure",
+ "success",
+ "missing disclosure",
+ "will join",
+ "already member",
+ "blocked"
+ ],
+ "actions": [
+ "sign in",
+ "create account",
+ "accept invitation",
+ "switch account",
+ "refresh invitation",
+ "return home"
+ ]
+ },
+ {
+ "file": "apps/sim/app/knowledge/github/setup/page.tsx",
+ "url": "/knowledge/github/setup",
+ "entry": "page.tsx",
+ "states": ["loading", "available accounts", "pending", "error", "complete"],
+ "actions": ["select installation", "connect"]
+ },
+ {
+ "file": "apps/sim/app/oauth-error/page.tsx",
+ "url": "/oauth-error",
+ "entry": "page.tsx",
+ "states": ["callback failure"],
+ "actions": ["close window"]
+ },
+ {
+ "file": "apps/sim/app/oauth/chat-complete/page.tsx",
+ "url": "/oauth/chat-complete",
+ "entry": "page.tsx",
+ "states": ["callback processing", "handoff"],
+ "actions": []
+ },
+ {
+ "file": "apps/sim/app/oauth/credential-connected/page.tsx",
+ "url": "/oauth/credential-connected",
+ "entry": "page.tsx",
+ "states": ["connected"],
+ "actions": ["close window"]
+ },
+ {
+ "file": "apps/sim/app/slack-search/connect/[token]/page.tsx",
+ "url": "/slack-search/connect/[token]",
+ "entry": "page.tsx",
+ "states": ["invitation unavailable", "workspace choice", "connecting", "error", "complete"],
+ "actions": ["connect Slack", "choose workspace"]
+ },
+ {
+ "file": "apps/sim/app/slack-search/install/[teamId]/page.tsx",
+ "url": "/slack-search/install/[teamId]",
+ "entry": "page.tsx",
+ "states": ["signed out", "unavailable", "install confirmation"],
+ "actions": ["install Slack", "return"]
+ }
+ ],
+ "native": [
+ {
+ "surface": "offline",
+ "states": ["offline", "DNS", "TLS", "timeout", "unreachable", "cloud status", "self-hosted"],
+ "actions": ["retry", "check status on Sim Cloud", "change server", "support"]
+ },
+ {
+ "surface": "server",
+ "states": ["current server", "invalid URL", "pending", "unchanged server", "failure"],
+ "actions": ["connect", "cancel", "Escape/window close"]
+ }
+ ],
+ "externalBoundaries": [
+ "Google, Microsoft and GitHub provider pages",
+ "SSO identity providers",
+ "CAPTCHA and password-manager UI",
+ "OS window chrome",
+ "resource authorization and account session cookies"
+ ],
+ "validationNotes": "State inventory records the retained surface contract; it is not a claim that every external provider or organization policy was exercised locally."
+}
diff --git a/apps/sim/auth-migration/route-parity.json b/apps/sim/auth-migration/route-parity.json
new file mode 100644
index 00000000000..83a328f7fc2
--- /dev/null
+++ b/apps/sim/auth-migration/route-parity.json
@@ -0,0 +1,12 @@
+{
+ "baseline": "a0f47b1e87b0a26d9071cad1ce592666b7e7de07",
+ "originalInventoryBase": "9c571fbcea10ed2001ea81fc967830e33dadcbf4",
+ "checkedRoutes": 1061,
+ "authEntries": 26,
+ "authPages": 24,
+ "authRedirects": 2,
+ "nativeAuthPages": 2,
+ "missing": [],
+ "removed": [],
+ "added": []
+}
diff --git a/apps/sim/auth-migration/surfaces.json b/apps/sim/auth-migration/surfaces.json
new file mode 100644
index 00000000000..bfcbd5c2348
--- /dev/null
+++ b/apps/sim/auth-migration/surfaces.json
@@ -0,0 +1,84 @@
+[
+ "apps/sim/app/(auth)/components/auth-legal-footer.tsx",
+ "apps/sim/app/(auth)/components/auth-loading-skeleton.tsx",
+ "apps/sim/app/(auth)/components/auth-navigation.tsx",
+ "apps/sim/app/(auth)/components/auth-shell.tsx",
+ "apps/sim/app/(auth)/components/auth-submit-button.tsx",
+ "apps/sim/app/(auth)/components/oauth-provider-checker.tsx",
+ "apps/sim/app/(auth)/components/social-login-buttons.tsx",
+ "apps/sim/app/(auth)/components/sso-login-button.tsx",
+ "apps/sim/app/(auth)/components/support-footer.tsx",
+ "apps/sim/app/(auth)/layout.tsx",
+ "apps/sim/app/(auth)/login/loading.tsx",
+ "apps/sim/app/(auth)/login/login-form.tsx",
+ "apps/sim/app/(auth)/login/page.tsx",
+ "apps/sim/app/(auth)/oauth/consent/consent-view.tsx",
+ "apps/sim/app/(auth)/oauth/consent/loading.tsx",
+ "apps/sim/app/(auth)/oauth/consent/page.tsx",
+ "apps/sim/app/(auth)/reset-password/loading.tsx",
+ "apps/sim/app/(auth)/reset-password/page.tsx",
+ "apps/sim/app/(auth)/reset-password/reset-password-content.tsx",
+ "apps/sim/app/(auth)/reset-password/reset-password-form.tsx",
+ "apps/sim/app/(auth)/signup/loading.tsx",
+ "apps/sim/app/(auth)/signup/page.tsx",
+ "apps/sim/app/(auth)/signup/registration-disabled.tsx",
+ "apps/sim/app/(auth)/signup/signup-form.tsx",
+ "apps/sim/app/(auth)/sso/loading.tsx",
+ "apps/sim/app/(auth)/sso/page.tsx",
+ "apps/sim/app/(auth)/verify/loading.tsx",
+ "apps/sim/app/(auth)/verify/page.tsx",
+ "apps/sim/app/(auth)/verify/verify-content.tsx",
+ "apps/sim/app/(interfaces)/chat/components/auth/email/email-auth.tsx",
+ "apps/sim/app/(interfaces)/chat/components/auth/password/password-auth.tsx",
+ "apps/sim/app/cli/auth/cli-auth-view.tsx",
+ "apps/sim/app/cli/auth/done/cli-auth-done-view.tsx",
+ "apps/sim/app/cli/auth/done/page.tsx",
+ "apps/sim/app/cli/auth/loading.tsx",
+ "apps/sim/app/cli/auth/page.tsx",
+ "apps/sim/app/credential-groups/complete/completion-handoff.tsx",
+ "apps/sim/app/credential-groups/complete/page.tsx",
+ "apps/sim/app/credential-groups/enroll/[token]/oauth-reconnect-link.tsx",
+ "apps/sim/app/credential-groups/enroll/[token]/oauth-toast.tsx",
+ "apps/sim/app/credential-groups/enroll/[token]/page.test.tsx",
+ "apps/sim/app/credential-groups/enroll/[token]/page.tsx",
+ "apps/sim/app/credential-groups/slack-complete/page.tsx",
+ "apps/sim/app/credential-groups/slack-complete/slack-completion.tsx",
+ "apps/sim/app/desktop/auth/authorize-handoff.tsx",
+ "apps/sim/app/desktop/auth/page.tsx",
+ "apps/sim/app/desktop/components/desktop-handoff-shell/desktop-handoff-shell.tsx",
+ "apps/sim/app/desktop/connect/complete/page.tsx",
+ "apps/sim/app/desktop/connect/connect-launcher.tsx",
+ "apps/sim/app/desktop/connect/page.tsx",
+ "apps/sim/app/desktop/connect/source-completion.tsx",
+ "apps/sim/app/desktop/connect/source-connect-launcher.tsx",
+ "apps/sim/app/desktop/connect/switch-account.tsx",
+ "apps/sim/app/desktop/done/page.tsx",
+ "apps/sim/app/f/[token]/opengraph-image.tsx",
+ "apps/sim/app/f/[token]/page.tsx",
+ "apps/sim/app/f/[token]/public-file-auth-shell.tsx",
+ "apps/sim/app/f/[token]/public-file-auth.tsx",
+ "apps/sim/app/f/[token]/public-file-email-auth.tsx",
+ "apps/sim/app/f/[token]/public-file-sso-auth.tsx",
+ "apps/sim/app/invite/[id]/invite.tsx",
+ "apps/sim/app/invite/[id]/loading.tsx",
+ "apps/sim/app/invite/[id]/page.tsx",
+ "apps/sim/app/invite/components/invitation-disclosure.tsx",
+ "apps/sim/app/invite/components/invitation-workspace-access.tsx",
+ "apps/sim/app/invite/components/layout.tsx",
+ "apps/sim/app/invite/components/status-card.tsx",
+ "apps/sim/app/knowledge/github/setup/page.tsx",
+ "apps/sim/app/knowledge/github/setup/setup.tsx",
+ "apps/sim/app/oauth-error/page.tsx",
+ "apps/sim/app/oauth/chat-complete/chat-complete-handoff.tsx",
+ "apps/sim/app/oauth/chat-complete/page.tsx",
+ "apps/sim/app/oauth/credential-connected/page.tsx",
+ "apps/sim/app/slack-search/connect/[token]/page.test.tsx",
+ "apps/sim/app/slack-search/connect/[token]/page.tsx",
+ "apps/sim/app/slack-search/connect/[token]/slack-search-onboarding.tsx",
+ "apps/sim/app/slack-search/install/[teamId]/page.test.tsx",
+ "apps/sim/app/slack-search/install/[teamId]/page.tsx",
+ "apps/sim/app/unsubscribe/unsubscribe.tsx",
+ "apps/sim/ee/sso/components/sso-auth.tsx",
+ "apps/sim/ee/sso/components/sso-form.test.tsx",
+ "apps/sim/ee/sso/components/sso-form.tsx"
+]
diff --git a/apps/sim/auth-migration/validation.json b/apps/sim/auth-migration/validation.json
new file mode 100644
index 00000000000..4084e3043d9
--- /dev/null
+++ b/apps/sim/auth-migration/validation.json
@@ -0,0 +1,380 @@
+{
+ "date": "2026-10-29",
+ "controlCleanup": {
+ "changes": [
+ "Centralized email placeholders, keyboard hints and autocomplete defaults in AuthEmailInput.",
+ "Centralized the default password placeholder in PasswordInput.",
+ "Refined the local visibility glyphs and localized provider logos and the consent checkmark.",
+ "Consolidated public-file SSO validation into AuthField."
+ ],
+ "validation": {
+ "lint": "pass across 27 workspaces",
+ "typeCheck": "pass across 27 workspaces",
+ "audits": "61 passed",
+ "desktopBuild": "pass",
+ "blockRegistry": "pass",
+ "focusedTests": "7 files and 24 tests passed, including the assistant-execution file",
+ "browser": [
+ "Recovery email placeholder and email input defaults verified in the running app.",
+ "Mouse and keyboard visibility controls switch the input type and accessible label.",
+ "Desktop and 390px mobile sign-in and recovery layouts visually verified.",
+ "Signup renders the shared email and password controls."
+ ],
+ "fullSuite": "Run before the final provider-logo and consent-icon import changes: 19/20 workspace tasks passed; App had 38,753 passing tests, 4 failures and 8 skips. Two failures were the previously documented executor timeouts; two were in unchanged tools/assistant-execution.test.ts, which passed in the final focused run. No unrelated test or timeout changes were made."
+ }
+ },
+ "base": "a0f47b1e87b0a26d9071cad1ce592666b7e7de07",
+ "runtime": {
+ "bun": "1.4.1",
+ "node": "24.19.0",
+ "next": "16.4.1"
+ },
+ "gates": [
+ {
+ "command": "bun run lint",
+ "status": "pass",
+ "workspaces": 27
+ },
+ {
+ "command": "bun run type-check",
+ "status": "pass",
+ "workspaces": 27
+ },
+ {
+ "command": "bun run check:audits",
+ "status": "pass",
+ "audits": 61
+ },
+ {
+ "command": "bun run build",
+ "cwd": "apps/sim",
+ "status": "pass",
+ "note": "Full production build passed before the final two-selector navigation-hover correction. That CSS correction was compiled through the same PostCSS pipeline, verified in the real shared-component browser fixture, and included in a fresh desktop build."
+ },
+ {
+ "command": "bun run build",
+ "cwd": "apps/desktop",
+ "status": "pass"
+ },
+ {
+ "command": "bun run apps/sim/scripts/check-block-registry.ts origin/staging",
+ "status": "pass"
+ },
+ {
+ "command": "git diff --check",
+ "status": "pass"
+ },
+ {
+ "command": "Focused Auth and shared invitation regressions",
+ "status": "pass",
+ "files": 10,
+ "tests": 65
+ },
+ {
+ "command": "bun run test",
+ "status": "failed",
+ "scriptTests": {
+ "filesPassed": 41,
+ "testsPassed": 634
+ },
+ "workspaceTasks": {
+ "passed": 19,
+ "total": 20
+ },
+ "appTests": {
+ "filesPassed": 3430,
+ "filesFailed": 2,
+ "testsPassed": 38755,
+ "testsFailed": 2,
+ "testsSkipped": 8
+ },
+ "failures": [
+ {
+ "file": "apps/sim/executor/execution/executor.test.ts",
+ "test": "does not run a stale branch inside a loop",
+ "result": "10000ms timeout"
+ },
+ {
+ "file": "apps/sim/executor/execution/test-hooks.test.ts",
+ "test": "delivers one call per loop iteration, in order",
+ "result": "10000ms timeout"
+ }
+ ],
+ "investigation": "These executor files and their production owners are unchanged. Both failures reproduce with bundled and official Node 24.19.0, including with the Vitest filesystem cache disabled. Both files pass all 22 tests under Node 23.10.0. No executor or test-timeout workaround was added to the Auth redesign."
+ }
+ ],
+ "routeParity": {
+ "baseline": "a0f47b1e87b0a26d9071cad1ce592666b7e7de07",
+ "originalInventoryBase": "9c571fbcea10ed2001ea81fc967830e33dadcbf4",
+ "checkedRoutes": 1061,
+ "authEntries": 26,
+ "authPages": 24,
+ "authRedirects": 2,
+ "nativeAuthPages": 2,
+ "missing": [],
+ "removed": [],
+ "added": []
+ },
+ "checks": {
+ "productionBrowser": [
+ {
+ "name": "Header preserves nested callback and invitation context",
+ "status": "pass",
+ "durationMs": 795
+ },
+ {
+ "name": "Password visibility preserves input and does not submit",
+ "status": "pass",
+ "durationMs": 95
+ },
+ {
+ "name": "Recovery keeps the email and original return URL",
+ "status": "pass",
+ "durationMs": 42
+ },
+ {
+ "name": "Real invalid sign-in response remains visible and associated",
+ "status": "pass",
+ "durationMs": 125
+ },
+ {
+ "name": "Auth is noindex over real HTTP",
+ "status": "pass",
+ "durationMs": 627
+ },
+ {
+ "name": "Login responsive 320",
+ "status": "pass",
+ "durationMs": 643
+ },
+ {
+ "name": "Login responsive 390",
+ "status": "pass",
+ "durationMs": 619
+ },
+ {
+ "name": "Login responsive 768",
+ "status": "pass",
+ "durationMs": 663
+ },
+ {
+ "name": "Login responsive 1280",
+ "status": "pass",
+ "durationMs": 611
+ },
+ {
+ "name": "Login responsive 1440",
+ "status": "pass",
+ "durationMs": 634
+ },
+ {
+ "name": "Signup retains the legacy redirect parameter in its header",
+ "status": "pass",
+ "durationMs": 586
+ },
+ {
+ "name": "Signup validation messages wrap without overlay at 320px",
+ "status": "pass",
+ "durationMs": 60
+ },
+ {
+ "name": "Public status route /reset-password",
+ "status": "pass",
+ "durationMs": 614
+ },
+ {
+ "name": "Public status route /oauth/consent",
+ "status": "pass",
+ "durationMs": 646
+ },
+ {
+ "name": "Public status route /desktop/auth",
+ "status": "pass",
+ "durationMs": 552
+ },
+ {
+ "name": "Public status route /desktop/done",
+ "status": "pass",
+ "durationMs": 553
+ },
+ {
+ "name": "Public status route /desktop/connect",
+ "status": "pass",
+ "durationMs": 552
+ },
+ {
+ "name": "Public status route /cli/auth/done",
+ "status": "pass",
+ "durationMs": 549
+ },
+ {
+ "name": "Public status route /oauth-error?error=access_denied",
+ "status": "pass",
+ "durationMs": 662
+ },
+ {
+ "name": "Public status route /oauth/credential-connected",
+ "status": "pass",
+ "durationMs": 655
+ },
+ {
+ "name": "Public status route /credential-groups/complete",
+ "status": "pass",
+ "durationMs": 695
+ },
+ {
+ "name": "Public status route /credential-groups/slack-complete",
+ "status": "pass",
+ "durationMs": 620
+ }
+ ],
+ "productionVisualAndIsolation": [
+ {
+ "name": "Mobile uses text navigation and support follows the form",
+ "status": "pass",
+ "durationMs": 759
+ },
+ {
+ "name": "Desktop uses pill navigation, 400px form and flow footer",
+ "status": "pass",
+ "durationMs": 653
+ },
+ {
+ "name": "App dark mode and brand variables do not change Auth typography or control colors",
+ "status": "pass",
+ "durationMs": 4
+ },
+ {
+ "name": "Auth suppresses configured deployment custom CSS",
+ "status": "pass",
+ "durationMs": 796
+ },
+ {
+ "name": "Auth content and robots metadata are server rendered without JavaScript",
+ "status": "pass",
+ "durationMs": 82
+ }
+ ],
+ "deploymentConfigurations": [
+ {
+ "name": "Server-only registration policy hides signup after hydration",
+ "status": "pass",
+ "durationMs": 6160
+ },
+ {
+ "name": "SSO-only policy keeps its primary action and removes email/recovery/signup controls",
+ "status": "pass",
+ "durationMs": 4543
+ },
+ {
+ "name": "No-methods deployment provides an explicit unavailable state",
+ "status": "pass",
+ "durationMs": 4535
+ },
+ {
+ "name": "All-provider configuration matches the specified ordering and mobile two-column layout",
+ "status": "pass",
+ "durationMs": 4603
+ }
+ ],
+ "realAccountLifecycle": [
+ {
+ "name": "Real signup reaches verification with callback retained",
+ "status": "pass"
+ },
+ {
+ "name": "Invalid OTP has visible associated feedback",
+ "status": "pass"
+ },
+ {
+ "name": "Mail-delivered OTP verifies once and returns to the requested page",
+ "status": "pass"
+ },
+ {
+ "name": "Recovery sends a real reset email and retains its neutral acknowledgement",
+ "status": "pass"
+ },
+ {
+ "name": "Reset validates mismatch, changes password, and returns to login",
+ "status": "pass"
+ },
+ {
+ "name": "New password signs in through the existing session handler",
+ "status": "pass"
+ }
+ ],
+ "sharedControls": [
+ {
+ "name": "Select keyboard skips disabled options and returns focus",
+ "status": "pass"
+ },
+ {
+ "name": "Search filters options and keeps keyboard selection",
+ "status": "pass"
+ },
+ {
+ "name": "Copy announces success then returns to Copy",
+ "status": "pass"
+ },
+ {
+ "name": "Account menu executes only its existing switch action",
+ "status": "pass"
+ },
+ {
+ "name": "OTP paste fills six slots and pending prevents duplicate actions",
+ "status": "pass"
+ },
+ {
+ "name": "Long menu and controls fit viewport 320",
+ "status": "pass"
+ },
+ {
+ "name": "Long menu and controls fit viewport 390",
+ "status": "pass"
+ },
+ {
+ "name": "Long menu and controls fit viewport 768",
+ "status": "pass"
+ },
+ {
+ "name": "Long menu and controls fit viewport 1440",
+ "status": "pass"
+ }
+ ],
+ "nativeElectron": [
+ {
+ "name": "Actual Electron loads the isolated offline screen and bundled fonts",
+ "status": "pass"
+ },
+ {
+ "name": "Native Change server opens the existing dialog with current URL",
+ "status": "pass"
+ },
+ {
+ "name": "Native server validation retains input and fits resized content",
+ "status": "pass"
+ },
+ {
+ "name": "Native Escape dismisses the existing server window",
+ "status": "pass"
+ }
+ ],
+ "navigationInteractionColors": [
+ {
+ "name": "Desktop navigation keeps dark fill and readable white text during hover and press",
+ "status": "pass"
+ },
+ {
+ "name": "Mobile navigation remains quiet text during hover",
+ "status": "pass"
+ }
+ ]
+ },
+ "liveInfrastructure": "Disposable local PostgreSQL, Mailpit SMTP, production Next server and actual Electron; no production accounts or external provider credentials used.",
+ "limitations": [
+ "The full monorepo test gate is not green because of the two Node 24 executor timeouts listed above.",
+ "Live external-provider and enterprise-tenant flows were not exercised. The route/state inventory is not an assertion that every provider or policy branch was executed.",
+ "Bun 1.4.1 is installed locally; the repository declares Bun 1.4.4. The Node 24 runtime used for validation was isolated without changing the global installation.",
+ "Browser screenshots and raw execution logs are local review artifacts; the checks and outcomes are recorded here without credentials, OTPs or handoff tokens."
+ ]
+}
diff --git a/apps/sim/components/branded-layout.tsx b/apps/sim/components/branded-layout.tsx
index a42e712f3d9..6aaa3d7d4ba 100644
--- a/apps/sim/components/branded-layout.tsx
+++ b/apps/sim/components/branded-layout.tsx
@@ -1,6 +1,8 @@
'use client'
import { useEffect } from 'react'
+import { usePathname } from 'next/navigation'
+import { isAuthSurfacePath } from '@/lib/navigation/auth-surfaces'
import { getBrandConfig } from '@/ee/whitelabeling'
interface BrandedLayoutProps {
@@ -8,28 +10,37 @@ interface BrandedLayoutProps {
}
export function BrandedLayout({ children }: BrandedLayoutProps) {
+ const pathname = usePathname()
+ const authSurface = isAuthSurfacePath(pathname)
+
useEffect(() => {
const config = getBrandConfig()
- // Update document title
if (config.name !== 'Sim') {
document.title = config.name
}
- // Update favicon
if (config.faviconUrl) {
const faviconLink = document.querySelector("link[rel*='icon']") as HTMLLinkElement
if (faviconLink) {
faviconLink.href = config.faviconUrl
}
}
+ }, [])
- // Load custom CSS if provided
- if (config.customCssUrl) {
+ useEffect(() => {
+ const config = getBrandConfig()
+ // Public resource routes can move between an Auth gate and their branded viewer.
+ const resourceRoute = pathname.startsWith('/f/') || pathname.startsWith('/chat/')
+ const syncCustomCss = () => {
const customCssId = 'custom-brand-css'
let customCssLink = document.getElementById(customCssId) as HTMLLinkElement
-
- if (!customCssLink) {
+ const authVisible = resourceRoute
+ ? Boolean(document.querySelector('[data-auth-root]'))
+ : authSurface
+ if (authVisible) {
+ customCssLink?.remove()
+ } else if (config.customCssUrl && !customCssLink) {
customCssLink = document.createElement('link')
customCssLink.id = customCssId
customCssLink.rel = 'stylesheet'
@@ -37,7 +48,12 @@ export function BrandedLayout({ children }: BrandedLayoutProps) {
document.head.appendChild(customCssLink)
}
}
- }, [])
+ syncCustomCss()
+ if (!resourceRoute) return
+ const observer = new MutationObserver(syncCustomCss)
+ observer.observe(document.body, { childList: true, subtree: true })
+ return () => observer.disconnect()
+ }, [authSurface, pathname])
return <>{children}>
}
diff --git a/apps/sim/ee/sso/components/sso-auth.tsx b/apps/sim/ee/sso/components/sso-auth.tsx
index 0af36c8de01..d032166d261 100644
--- a/apps/sim/ee/sso/components/sso-auth.tsx
+++ b/apps/sim/ee/sso/components/sso-auth.tsx
@@ -1,14 +1,14 @@
'use client'
-import { type KeyboardEvent, useState } from 'react'
-import { cn, Input, Label } from '@sim/emcn'
+import { useState } from 'react'
+import { AuthEmailInput, AuthField, AuthHeader, AuthStack } from '@sim/auth-ui'
import { createLogger } from '@sim/logger'
import { useRouter } from 'next/navigation'
import { ApiClientError } from '@/lib/api/client/errors'
import { requestJson } from '@/lib/api/client/request'
import { chatSSOContract } from '@/lib/api/contracts/chats'
import { quickValidateEmail } from '@/lib/messaging/email/validation'
-import { AuthSubmitButton } from '@/app/(auth)/components'
+import { AuthShell, AuthSubmitButton } from '@/app/(auth)/components'
const logger = createLogger('SSOAuth')
@@ -39,13 +39,6 @@ export default function SSOAuth({ identifier }: SSOAuthProps) {
const [showEmailValidationError, setShowEmailValidationError] = useState(false)
const [isLoading, setIsLoading] = useState(false)
- const handleKeyDown = (e: KeyboardEvent
) => {
- if (e.key === 'Enter') {
- e.preventDefault()
- handleAuthenticate()
- }
- }
-
const handleEmailChange = (e: React.ChangeEvent) => {
const newEmail = e.target.value
setEmail(newEmail)
@@ -54,6 +47,7 @@ export default function SSOAuth({ identifier }: SSOAuthProps) {
}
const handleAuthenticate = async () => {
+ if (isLoading) return
const emailValidationErrors = validateEmailField(email)
setEmailErrors(emailValidationErrors)
setShowEmailValidationError(emailValidationErrors.length > 0)
@@ -95,62 +89,41 @@ export default function SSOAuth({ identifier }: SSOAuthProps) {
}
return (
-
-
-
-
-
- SSO Authentication
-
-
- This chat requires SSO authentication
-
-
-
-
-
-
+ 0}
+ />
+
+
+ Continue with SSO
+
+
+
+
)
}
diff --git a/apps/sim/ee/sso/components/sso-form.test.tsx b/apps/sim/ee/sso/components/sso-form.test.tsx
index b9c833c56e5..66bc64fea87 100644
--- a/apps/sim/ee/sso/components/sso-form.test.tsx
+++ b/apps/sim/ee/sso/components/sso-form.test.tsx
@@ -1,7 +1,7 @@
/**
* @vitest-environment jsdom
*/
-import type { ButtonHTMLAttributes, InputHTMLAttributes, ReactNode } from 'react'
+import type { ReactNode } from 'react'
import { apiClientRequestMock } from '@sim/testing/mocks/api-client-request.mock'
import { authClientMock } from '@sim/testing/mocks/auth-client.mock'
import { envMockFns, resetEnvMock } from '@sim/testing/mocks/env.mock'
@@ -13,26 +13,10 @@ vi.mock('@/lib/api/client/request', () => apiClientRequestMock)
vi.mock('next/navigation', () => nextNavigationMock)
-vi.mock('next/link', () => ({
- default: ({ href, children }: { href: string; children?: ReactNode }) => (
- {children}
- ),
-}))
-
-vi.mock('@sim/emcn', () => ({
- Button: ({ children, ...props }: ButtonHTMLAttributes) => (
-
- {children}
-
- ),
- Input: (props: InputHTMLAttributes) => ,
- Label: ({ children }: { children?: ReactNode }) => {children} ,
- cn: (...values: unknown[]) => values.filter(Boolean).join(' '),
-}))
-
vi.mock('@/lib/auth/client', () => authClientMock)
vi.mock('@/app/(auth)/components', () => ({
+ AuthLegalFooter: () => null,
AuthFormMessage: ({ children }: { children?: ReactNode }) => {children}
,
AuthSubmitButton: ({
children,
diff --git a/apps/sim/ee/sso/components/sso-form.tsx b/apps/sim/ee/sso/components/sso-form.tsx
index 86e6c88efb0..9e17c3e81c7 100644
--- a/apps/sim/ee/sso/components/sso-form.tsx
+++ b/apps/sim/ee/sso/components/sso-form.tsx
@@ -1,9 +1,16 @@
'use client'
import { useEffect, useState } from 'react'
-import { Button, cn, Input, Label } from '@sim/emcn'
+import {
+ AuthDivider,
+ AuthEmailInput,
+ AuthField,
+ AuthHeader,
+ AuthLinkButton,
+ AuthNavPrompt,
+ AuthStack,
+} from '@sim/auth-ui'
import { createLogger } from '@sim/logger'
-import Link from 'next/link'
import { useSearchParams } from 'next/navigation'
import { isApiClientError } from '@/lib/api/client/errors'
import { requestJson } from '@/lib/api/client/request'
@@ -14,7 +21,7 @@ import { validateCallbackUrl } from '@/lib/core/security/input-validation'
import { quickValidateEmail } from '@/lib/messaging/email/validation'
import { captureClientEvent } from '@/lib/posthog/client'
import { DEFAULT_POST_AUTH_ROUTE } from '@/app/(auth)/auth-redirect'
-import { AuthFormMessage, AuthSubmitButton } from '@/app/(auth)/components'
+import { AuthFormMessage, AuthLegalFooter, AuthSubmitButton } from '@/app/(auth)/components'
const logger = createLogger('SSOForm')
const SSO_SIGN_IN_ERROR = 'Unable to start SSO. Check your email and try again.'
@@ -128,6 +135,7 @@ function SSOFormContent({
async function onSubmit(e: React.FormEvent) {
e.preventDefault()
+ if (isLoading) return
setIsLoading(true)
setFormError(null)
@@ -187,126 +195,47 @@ function SSOFormContent({
}
return (
- <>
-
-
+
+
+ {formError && {formError} }
+
- Sign in with SSO
-
-
- Enter your work email to continue
-
-
-
-
- {formError && (
-
- )}
-
-
-
-
- Work email
-
-
- {hasEmailError && (
-
- {emailErrors.map((error) => (
-
{error}
- ))}
-
- )}
-
-
-
+
+
Continue with SSO
-
{emailEnabled && (
<>
-
-
-
-
-
- Sign in with email
-
-
-
+
+
+ Sign in with email
+
>
)}
-
{emailEnabled && !registrationDisabled && (
-
- Don't have an account?
-
- Sign up
-
-
+
)}
-
-
- By signing in, you agree to our{' '}
-
- Terms of Service
- {' '}
- and{' '}
-
- Privacy Policy
-
-
- >
+
+
)
}
diff --git a/apps/sim/lib/navigation/auth-surfaces.ts b/apps/sim/lib/navigation/auth-surfaces.ts
new file mode 100644
index 00000000000..ca886bbde0b
--- /dev/null
+++ b/apps/sim/lib/navigation/auth-surfaces.ts
@@ -0,0 +1,28 @@
+import { isPathOrDescendant } from '@/lib/navigation/paths'
+
+const AUTH_SURFACE_ROOTS = [
+ '/login',
+ '/signup',
+ '/verify',
+ '/reset-password',
+ '/sso',
+ '/oauth',
+ '/oauth-error',
+ '/invite',
+ '/enterprise/claim',
+ '/unsubscribe',
+ '/desktop/auth',
+ '/desktop/done',
+ '/desktop/connect',
+ '/cli/auth',
+ '/credential-groups',
+ '/slack-search',
+ '/knowledge/github/setup',
+ '/f',
+ '/chat',
+] as const
+
+/** Routes whose account or resource-access UI is governed by the isolated Auth system. */
+export function isAuthSurfacePath(pathname: string): boolean {
+ return AUTH_SURFACE_ROOTS.some((root) => isPathOrDescendant(pathname, root))
+}
diff --git a/apps/sim/next.config.ts b/apps/sim/next.config.ts
index 87b7cbd3427..f516f9b4aca 100644
--- a/apps/sim/next.config.ts
+++ b/apps/sim/next.config.ts
@@ -274,6 +274,7 @@ const nextConfig: NextConfig = {
'@t3-oss/env-core',
'@sim/db',
'@sim/emcn',
+ '@sim/auth-ui',
'@sim/workflow-renderer',
],
async headers() {
diff --git a/apps/sim/package.json b/apps/sim/package.json
index 77e1ddfebf5..f99a1c9675f 100644
--- a/apps/sim/package.json
+++ b/apps/sim/package.json
@@ -217,7 +217,6 @@
"https-proxy-agent": "7.0.6",
"idb-keyval": "6.3.0",
"imapflow": "1.7.8",
- "input-otp": "^1.5.0",
"ioredis": "^5.6.0",
"isolated-vm": "6.2.0",
"jose": "6.2.3",
@@ -284,7 +283,8 @@
"yauzl": "3.4.0",
"yjs": "13.6.33",
"zod": "4.3.6",
- "zustand": "^5.0.15"
+ "zustand": "^5.0.15",
+ "@sim/auth-ui": "workspace:*"
},
"devDependencies": {
"@next/env": "16.4.1",
diff --git a/apps/sim/proxy.test.ts b/apps/sim/proxy.test.ts
index 4f2705eeed9..59c9f3d2369 100644
--- a/apps/sim/proxy.test.ts
+++ b/apps/sim/proxy.test.ts
@@ -228,9 +228,22 @@ describe('proxy indexing policy', () => {
expect(robotsTag('/library.md/some-article')).toBeNull()
})
- it('leaves a self-hosted non-landing public page indexable', () => {
- setEnvFlags({ isHosted: false })
- expect(robotsTag('/login')).toBeNull()
+ it.each([
+ '/login',
+ '/signup',
+ '/verify',
+ '/oauth/consent',
+ '/invite/example',
+ '/desktop/auth',
+ '/cli/auth',
+ '/credential-groups/enroll/example',
+ '/f/example',
+ '/chat/example',
+ ])('keeps Auth out of search results on every deployment: %s', (pathname) => {
+ for (const isHosted of [false, true]) {
+ setEnvFlags({ isHosted })
+ expect(robotsTag(pathname)).toBe('noindex, nofollow')
+ }
})
})
diff --git a/apps/sim/proxy.ts b/apps/sim/proxy.ts
index 2bb6396180e..93e8aea8b7b 100644
--- a/apps/sim/proxy.ts
+++ b/apps/sim/proxy.ts
@@ -9,6 +9,7 @@ import { resolveSimMcpHostPath } from '@/lib/api/mcp/host-routing'
import { SIM_MCP_ROUTE_PATH } from '@/lib/api/mcp/urls'
import { getBrowserExtensionOrigins } from '@/lib/core/security/extension'
import { isMarketingPath } from '@/lib/landing/routes'
+import { isAuthSurfacePath } from '@/lib/navigation/auth-surfaces'
import { APP_ENTRY_PATH, isAppSurfacePath, isNoindexPath } from '@/lib/navigation/paths'
import { isOAuthAuthorizationCallback, resolveAuthRedirect } from '@/app/(auth)/auth-redirect'
import { getEnv } from './lib/core/config/env'
@@ -471,6 +472,7 @@ function applyIndexingPolicy(request: NextRequest, response: NextResponse): Next
if (
isNonCanonicalSimHost(host) ||
isNoindexPath(pathname) ||
+ isAuthSurfacePath(pathname) ||
(!isHosted && isMarketingPath(pathname))
) {
response.headers.set('X-Robots-Tag', 'noindex, nofollow')
diff --git a/biome.json b/biome.json
index 9086a717c9a..631ce6bb75b 100644
--- a/biome.json
+++ b/biome.json
@@ -154,7 +154,7 @@
},
"overrides": [
{
- "includes": ["apps/sim/app/_styles/tailwind.css"],
+ "includes": ["apps/sim/app/_styles/tailwind.css", "packages/auth-ui/src/utilities.css"],
"linter": { "enabled": false },
"formatter": { "enabled": false }
},
diff --git a/bun.lock b/bun.lock
index aaa33a789ff..78930e39690 100644
--- a/bun.lock
+++ b/bun.lock
@@ -50,6 +50,7 @@
"@lydell/node-pty": "1.2.0-beta.12",
"@lydell/node-pty-darwin-arm64": "1.2.0-beta.12",
"@lydell/node-pty-darwin-x64": "1.2.0-beta.12",
+ "@sim/auth-ui": "workspace:*",
"@sim/browser-protocol": "workspace:*",
"@sim/desktop-bridge": "workspace:*",
"@sim/logger": "workspace:*",
@@ -274,6 +275,7 @@
"@react-email/render": "2.1.0",
"@sim/audit": "workspace:*",
"@sim/auth": "workspace:*",
+ "@sim/auth-ui": "workspace:*",
"@sim/browser-extension-protocol": "workspace:*",
"@sim/browser-protocol": "workspace:*",
"@sim/db": "workspace:*",
@@ -354,7 +356,6 @@
"https-proxy-agent": "7.0.6",
"idb-keyval": "6.3.0",
"imapflow": "1.7.8",
- "input-otp": "^1.5.0",
"ioredis": "^5.6.0",
"isolated-vm": "6.2.0",
"jose": "6.2.3",
@@ -500,6 +501,26 @@
"typescript": "^7.0.2",
},
},
+ "packages/auth-ui": {
+ "name": "@sim/auth-ui",
+ "version": "0.1.0",
+ "dependencies": {
+ "@radix-ui/react-dropdown-menu": "2.2.1",
+ "@radix-ui/react-popover": "1.3.1",
+ "clsx": "^2.1.1",
+ "input-otp": "1.5.0",
+ },
+ "devDependencies": {
+ "@sim/tsconfig": "workspace:*",
+ "@types/react": "^19",
+ "@types/react-dom": "^19",
+ "typescript": "^7.0.2",
+ },
+ "peerDependencies": {
+ "react": "^19",
+ "react-dom": "^19",
+ },
+ },
"packages/browser-extension-protocol": {
"name": "@sim/browser-extension-protocol",
"version": "0.1.0",
@@ -614,7 +635,6 @@
"@types/react-dom": "^19",
"class-variance-authority": "0.7.1",
"framer-motion": "^12.5.0",
- "input-otp": "1.5.0",
"next": "16.4.1",
"prismjs": "^1.30.0",
"react": "19.2.4",
@@ -639,7 +659,6 @@
"@tanstack/react-virtual": "^3.13.24",
"class-variance-authority": "^0.7.1",
"framer-motion": "^12.5.0",
- "input-otp": "^1.5.0",
"next": ">=15",
"prismjs": "^1.30.0",
"react": "^19",
@@ -1993,6 +2012,8 @@
"@sim/auth": ["@sim/auth@workspace:packages/auth"],
+ "@sim/auth-ui": ["@sim/auth-ui@workspace:packages/auth-ui"],
+
"@sim/browser-extension-protocol": ["@sim/browser-extension-protocol@workspace:packages/browser-extension-protocol"],
"@sim/browser-protocol": ["@sim/browser-protocol@workspace:packages/browser-protocol"],
diff --git a/packages/auth-ui/design/contract.json b/packages/auth-ui/design/contract.json
new file mode 100644
index 00000000000..6735b962c47
--- /dev/null
+++ b/packages/auth-ui/design/contract.json
@@ -0,0 +1,93 @@
+{
+ "runtimeOwner": "@sim/auth-ui",
+ "styleOwner": "src/styles.module.css",
+ "snapshot": "design/tokens.json",
+ "architecture": {
+ "static": ["primitives.tsx", "compositions.tsx", "icons.tsx", "wordmark.tsx"],
+ "interactive": ["controls.tsx", "code-entry.tsx"],
+ "hostAdapters": "apps/sim/app/(auth)/components; apps/desktop/src/renderer",
+ "rule": "Shared visual elements live in this package. Route-specific operations stay colocated with their page. No auth/session logic or App/Docs/Landing theme imports in the package.",
+ "isolation": [
+ "CSS Modules",
+ "root-owned Auth variables",
+ "au: prefixed utilities without preflight",
+ "local licensed fonts",
+ "portals in AuthRoot",
+ "deployment CSS suppressed only while Auth is visible",
+ "identity, favicon and legal/support endpoints retained"
+ ]
+ },
+ "responsive": {
+ "desktop": {
+ "formMaxWidth": 400,
+ "navigationMaxWidth": 1280,
+ "navigationHeight": 56,
+ "pagePadding": "48px 24px",
+ "groupGap": 20,
+ "inputFontSize": 14,
+ "navigationAction": "32px ink pill",
+ "support": "flow footer after flexible main"
+ },
+ "mobile": {
+ "breakpointMax": 767,
+ "gutters": 16,
+ "mainTop": 24,
+ "groupGap": 24,
+ "visibleControlHeight": 32,
+ "touchTargetHeight": 44,
+ "inputFontSize": 16,
+ "navigationAction": "quiet text link",
+ "support": "after form, in document flow"
+ },
+ "all": {
+ "fieldGap": 20,
+ "labelGap": 8,
+ "controlRadius": 8,
+ "controlInset": 10,
+ "quietControl": 28,
+ "glyph": 16,
+ "otpSlot": 32,
+ "otpGap": 8,
+ "providerColumns": 2,
+ "providerGap": 8,
+ "menuPadding": 4,
+ "menuRadius": 10,
+ "menuRowMinHeight": 30,
+ "menuRowRadius": 6,
+ "menuBlur": 20
+ }
+ },
+ "tokens": {
+ "canvas": "--color-background",
+ "primary": "--color-app-text-primary",
+ "secondary": "--color-app-text-secondary",
+ "reading": "--color-app-reading",
+ "overlay": "--color-app-overlay",
+ "inputBorder": "--color-border",
+ "secondaryBorder": "--color-app-border",
+ "hover": "--color-app-hover",
+ "selected": "--color-app-selected",
+ "pressed": "--color-app-pressed",
+ "focus": "--blue",
+ "error": "#C83232 from Auth state boards",
+ "disabledOpacity": 0.4
+ },
+ "practicesReviewed": {
+ "date": "2026-10-29",
+ "documentationVersion": "16.4.3",
+ "installedVersion": "16.4.1",
+ "sources": [
+ "https://nextjs.org/docs/app/getting-started/project-structure",
+ "https://nextjs.org/docs/app/getting-started/server-and-client-components",
+ "https://nextjs.org/docs/app/api-reference/functions/generate-metadata",
+ "https://developers.google.com/search/docs/crawling-indexing/block-indexing"
+ ],
+ "decisions": [
+ "Keep server-rendered route/layout composition; isolate interactive controls behind client boundaries.",
+ "Colocate route-specific forms and keep the reusable visual system in a workspace package.",
+ "Keep Auth noindex/nofollow through metadata and HTTP indexing policy; no Auth sitemap, structured-data marketing, or agent discovery additions.",
+ "Preserve full-document Auth/App navigation boundaries.",
+ "Serve local WOFF2 with font-display swap; avoid adding a network font or analytics dependency."
+ ]
+ }
+}
diff --git a/packages/auth-ui/design/tokens.json b/packages/auth-ui/design/tokens.json
new file mode 100644
index 00000000000..c5e404a51b3
--- /dev/null
+++ b/packages/auth-ui/design/tokens.json
@@ -0,0 +1,2005 @@
+{
+ "tokens": [
+ {
+ "name": "--neutral-white",
+ "type": "color",
+ "value": "rgb(255 255 255)",
+ "description": "Figma: primitive/neutral/white; mode Default."
+ },
+ {
+ "name": "--neutral-pearl",
+ "type": "color",
+ "value": "#FAFAFA",
+ "description": "Figma: primitive/neutral/pearl; mode Default."
+ },
+ {
+ "name": "--neutral-silver",
+ "type": "color",
+ "value": "#F5F5F5",
+ "description": "Figma: primitive/neutral/silver; mode Default."
+ },
+ {
+ "name": "--neutral-gray",
+ "type": "color",
+ "value": "#6E6E6E",
+ "description": "Figma: primitive/neutral/gray; mode Default."
+ },
+ {
+ "name": "--neutral-graphite",
+ "type": "color",
+ "value": "#1D1D1D",
+ "description": "Figma: primitive/neutral/graphite; mode Default."
+ },
+ {
+ "name": "--neutral-black",
+ "type": "color",
+ "value": "rgb(0 0 0)",
+ "description": "Figma: primitive/neutral/black; mode Default."
+ },
+ {
+ "name": "--blue",
+ "type": "color",
+ "value": "rgb(0 102 204)",
+ "description": "Figma: primitive/blue; mode Default."
+ },
+ {
+ "name": "--black-4",
+ "type": "color",
+ "value": "rgb(0 0 0 / 4%)",
+ "description": "Figma: primitive/black/4; mode Default."
+ },
+ {
+ "name": "--black-6",
+ "type": "color",
+ "value": "rgb(0 0 0 / 6%)",
+ "description": "Figma: primitive/black/6; mode Default."
+ },
+ {
+ "name": "--black-8",
+ "type": "color",
+ "value": "rgb(0 0 0 / 8%)",
+ "description": "Figma: primitive/black/8; mode Default."
+ },
+ {
+ "name": "--black-12",
+ "type": "color",
+ "value": "rgb(0 0 0 / 12%)",
+ "description": "Figma: primitive/black/12; mode Default."
+ },
+ {
+ "name": "--white-0",
+ "type": "color",
+ "value": "rgb(255 255 255 / 0%)",
+ "description": "Figma: primitive/white/0; mode Default."
+ },
+ {
+ "name": "--white-30",
+ "type": "color",
+ "value": "rgb(255 255 255 / 30%)",
+ "description": "Figma: primitive/white/30; mode Default."
+ },
+ {
+ "name": "--white-70",
+ "type": "color",
+ "value": "rgb(255 255 255 / 70%)",
+ "description": "Figma: primitive/white/70; mode Default."
+ },
+ {
+ "name": "--white-85",
+ "type": "color",
+ "value": "rgb(255 255 255 / 85%)",
+ "description": "Figma: primitive/white/85; mode Default."
+ },
+ {
+ "name": "--pearl-80",
+ "type": "color",
+ "value": "rgb(255 255 255 / 80%)",
+ "description": "Figma: primitive/pearl/80; mode Default."
+ },
+ {
+ "name": "--color-surface-muted",
+ "type": "color",
+ "value": "var(--ink-4)",
+ "description": "Figma: color/surface/muted; mode Default."
+ },
+ {
+ "name": "--color-glass-fallback",
+ "type": "color",
+ "value": "var(--neutral-pearl)",
+ "description": "Figma: color/glass/fallback; mode Default."
+ },
+ {
+ "name": "--color-glass-highlight",
+ "type": "color",
+ "value": "var(--white-70)",
+ "description": "Figma: color/glass/highlight; mode Default."
+ },
+ {
+ "name": "--color-fade-edge",
+ "type": "color",
+ "value": "var(--white-85)",
+ "description": "Figma: color/fade/edge; mode Default."
+ },
+ {
+ "name": "--color-fade-middle",
+ "type": "color",
+ "value": "var(--white-30)",
+ "description": "Figma: color/fade/middle; mode Default."
+ },
+ {
+ "name": "--color-fade-clear",
+ "type": "color",
+ "value": "var(--white-0)",
+ "description": "Figma: color/fade/clear; mode Default."
+ },
+ {
+ "name": "--black-72",
+ "type": "color",
+ "value": "rgb(0 0 0 / 72%)",
+ "description": "Figma: primitive/black/72; mode Default."
+ },
+ {
+ "name": "--white-10",
+ "type": "color",
+ "value": "rgb(255 255 255 / 10%)",
+ "description": "Figma: primitive/white/10; mode Default."
+ },
+ {
+ "name": "--white-20",
+ "type": "color",
+ "value": "rgb(255 255 255 / 20%)",
+ "description": "Figma: primitive/white/20; mode Default."
+ },
+ {
+ "name": "--color-button-label",
+ "type": "color",
+ "value": "var(--neutral-white)",
+ "description": "Figma: color/button/label; mode Default."
+ },
+ {
+ "name": "--color-split-hover",
+ "type": "color",
+ "value": "var(--white-10)",
+ "description": "Figma: color/split/hover; mode Default."
+ },
+ {
+ "name": "--color-split-divider",
+ "type": "color",
+ "value": "var(--white-20)",
+ "description": "Figma: color/split/divider; mode Default."
+ },
+ {
+ "name": "--neutral-ink",
+ "type": "color",
+ "value": "rgb(10 10 10)",
+ "description": "Figma: neutral/ink; mode Default."
+ },
+ {
+ "name": "--neutral-warm",
+ "type": "color",
+ "value": "rgb(249 248 246)",
+ "description": "Figma: neutral/warm; mode Default."
+ },
+ {
+ "name": "--orange",
+ "type": "color",
+ "value": "rgb(255 99 8)",
+ "description": "Figma: orange; mode Default."
+ },
+ {
+ "name": "--ink-hover",
+ "type": "color",
+ "value": "rgb(9 9 9)",
+ "description": "Figma: ink/hover; mode Default."
+ },
+ {
+ "name": "--white-90",
+ "type": "color",
+ "value": "rgb(229 229 229)",
+ "description": "Figma: white/90; mode Default."
+ },
+ {
+ "name": "--warm-3",
+ "type": "color",
+ "value": "rgb(242 241 239)",
+ "description": "Figma: warm/3; mode Default."
+ },
+ {
+ "name": "--warm-7",
+ "type": "color",
+ "value": "rgb(232 231 229)",
+ "description": "Figma: warm/7; mode Default."
+ },
+ {
+ "name": "--orange-10",
+ "type": "color",
+ "value": "rgb(255 99 8 / 10%)",
+ "description": "Figma: orange/10; mode Default."
+ },
+ {
+ "name": "--black-22",
+ "type": "color",
+ "value": "rgb(0 0 0 / 22%)",
+ "description": "Figma: black/22; mode Default."
+ },
+ {
+ "name": "--ink-4",
+ "type": "color",
+ "value": "rgb(10 10 10 / 4%)",
+ "description": "Figma: ink/4; mode Default."
+ },
+ {
+ "name": "--ink-6",
+ "type": "color",
+ "value": "rgb(10 10 10 / 6%)",
+ "description": "Figma: ink/6; mode Default."
+ },
+ {
+ "name": "--ink-8",
+ "type": "color",
+ "value": "rgb(10 10 10 / 8%)",
+ "description": "Figma: ink/8; mode Default."
+ },
+ {
+ "name": "--ink-15",
+ "type": "color",
+ "value": "rgb(10 10 10 / 15%)",
+ "description": "Figma: ink/15; mode Default."
+ },
+ {
+ "name": "--ink-20",
+ "type": "color",
+ "value": "rgb(10 10 10 / 20%)",
+ "description": "Figma: ink/20; mode Default."
+ },
+ {
+ "name": "--ink-30",
+ "type": "color",
+ "value": "rgb(10 10 10 / 30%)",
+ "description": "Figma: ink/30; mode Default."
+ },
+ {
+ "name": "--ink-80",
+ "type": "color",
+ "value": "rgb(10 10 10 / 80%)",
+ "description": "Figma: ink/80; mode Default."
+ },
+ {
+ "name": "--color-control-text",
+ "type": "color",
+ "value": "var(--neutral-ink)",
+ "description": "Figma: color/control/text; mode Default."
+ },
+ {
+ "name": "--color-button-label-hover",
+ "type": "color",
+ "value": "var(--white-90)",
+ "description": "Figma: color/button/label/hover; mode Default."
+ },
+ {
+ "name": "--color-icon-background",
+ "type": "color",
+ "value": "var(--white-0)",
+ "description": "Figma: color/icon/background; mode Default."
+ },
+ {
+ "name": "--color-icon-background-hover",
+ "type": "color",
+ "value": "var(--ink-6)",
+ "description": "Figma: color/icon/background/hover; mode Default."
+ },
+ {
+ "name": "--color-announcement-badge",
+ "type": "color",
+ "value": "var(--orange)",
+ "description": "Figma: color/announcement/badge; mode Default."
+ },
+ {
+ "name": "--color-announcement-badge-background",
+ "type": "color",
+ "value": "var(--orange-10)",
+ "description": "Figma: color/announcement/badge/background; mode Default."
+ },
+ {
+ "name": "--color-announcement-separator",
+ "type": "color",
+ "value": "var(--ink-20)",
+ "description": "Figma: Archive/color/announcement/separator; mode Default."
+ },
+ {
+ "name": "--color-announcement-description-hover",
+ "type": "color",
+ "value": "var(--ink-80)",
+ "description": "Figma: color/announcement/description/hover; mode Default."
+ },
+ {
+ "name": "--color-menu-border",
+ "type": "color",
+ "value": "var(--ink-6)",
+ "description": "Figma: Archive/color/menu/border; mode Default."
+ },
+ {
+ "name": "--color-menu-hover",
+ "type": "color",
+ "value": "var(--ink-4)",
+ "description": "Figma: Archive/color/menu/hover; mode Default."
+ },
+ {
+ "name": "--ink-60",
+ "type": "color",
+ "value": "rgb(10 10 10 / 60%)",
+ "description": "Figma: ink/60; mode Default."
+ },
+ {
+ "name": "--color-nav-link-muted",
+ "type": "color",
+ "value": "var(--ink-60)",
+ "description": "Neighboring navigation links while one link is hovered or keyboard-focused. CSS --color-nav-link-muted: var(--ink-60). Figma: color/nav/link/muted; mode Default."
+ },
+ {
+ "name": "--color-nav-dropdown",
+ "type": "color",
+ "value": "var(--color-glass-floating)",
+ "description": "Landing dropdowns inherit the same neutral floating glass as App menus."
+ },
+ {
+ "name": "--color-nav-backdrop",
+ "type": "color",
+ "value": "rgb(232 232 232 / 40%)",
+ "description": "Figma: color/nav/backdrop; mode Default."
+ },
+ {
+ "name": "--color-nav-background",
+ "type": "color",
+ "value": "var(--neutral-white)",
+ "description": "Figma: color/nav/background; mode Default."
+ },
+ {
+ "name": "--white-80",
+ "type": "color",
+ "value": "rgb(255 255 255 / 80%)",
+ "description": "Figma: primitive/white/80; mode Default."
+ },
+ {
+ "name": "--color-background",
+ "type": "color",
+ "value": "var(--neutral-white)",
+ "description": "Shared Landing and App canvas: neutral white (#FFFFFF)."
+ },
+ {
+ "name": "--color-surface",
+ "type": "color",
+ "value": "var(--neutral-white)",
+ "description": "Measured from https://devin.ai/ on 2026-10-06. #FFFFFF. Figma: color/surface; mode Default."
+ },
+ {
+ "name": "--color-surface-inverse",
+ "type": "color",
+ "value": "var(--neutral-black)",
+ "description": "Measured from https://devin.ai/ on 2026-10-06. #000000. Figma: color/surface/inverse; mode Default."
+ },
+ {
+ "name": "--color-text-primary",
+ "type": "color",
+ "value": "var(--neutral-ink)",
+ "description": "Shared Landing and App primary text: neutral ink (#0A0A0A). Used for identity, headings and content."
+ },
+ {
+ "name": "--color-text-secondary",
+ "type": "color",
+ "value": "var(--ink-60)",
+ "description": "Shared neutral ink at 60% for secondary copy, quiet controls and chart series."
+ },
+ {
+ "name": "--color-text-inverse",
+ "type": "color",
+ "value": "var(--neutral-silver)",
+ "description": "Measured from https://devin.ai/ on 2026-10-06. #FFFFFF. Figma: color/text/inverse; mode Default."
+ },
+ {
+ "name": "--color-accent",
+ "type": "color",
+ "value": "var(--blue)",
+ "description": "Measured from https://devin.ai/ on 2026-10-06. #317CFF. Figma: color/accent; mode Default."
+ },
+ {
+ "name": "--color-border",
+ "type": "color",
+ "value": "var(--black-12)",
+ "description": "Shared strong boundary: neutral black at 12%."
+ },
+ {
+ "name": "--color-border-subtle",
+ "type": "color",
+ "value": "var(--black-6)",
+ "description": "Measured from https://devin.ai/ on 2026-10-06. #000000 at 6% opacity. Figma: color/border/subtle; mode Default."
+ },
+ {
+ "name": "--color-tint",
+ "type": "color",
+ "value": "var(--black-4)",
+ "description": "Measured from https://devin.ai/ on 2026-10-06. #000000 at 4% opacity. Figma: color/tint; mode Default."
+ },
+ {
+ "name": "--color-tint-hover",
+ "type": "color",
+ "value": "var(--black-6)",
+ "description": "Measured from https://devin.ai/ on 2026-10-06. #000000 at 6% opacity. Figma: color/tint/hover; mode Default."
+ },
+ {
+ "name": "--color-glass",
+ "type": "color",
+ "value": "var(--white-80)",
+ "description": "Shared Landing and App shell glass: neutral white at 80%. Blur belongs to layered chrome; reading surfaces stay clearer."
+ },
+ {
+ "name": "--color-shadow",
+ "type": "color",
+ "value": "var(--black-8)",
+ "description": "Measured from https://devin.ai/ on 2026-10-06. #000000 at 6% opacity. Figma: color/shadow; mode Default."
+ },
+ {
+ "name": "--color-shadow-floating",
+ "type": "color",
+ "value": "var(--black-12)",
+ "description": "Measured from https://devin.ai/ on 2026-10-06. #000000 at 25% opacity. Figma: color/shadow/floating; mode Default."
+ },
+ {
+ "name": "--color-nav-glass",
+ "type": "color",
+ "value": "var(--white-80)",
+ "description": "Neutral white at 80%, shared with shell glass. No blue or pearl tint."
+ },
+ {
+ "name": "--color-button-primary",
+ "type": "color",
+ "value": "var(--neutral-ink)",
+ "description": "Figma: color/button/primary; mode Default."
+ },
+ {
+ "name": "--color-button-primary-hover",
+ "type": "color",
+ "value": "var(--ink-hover)",
+ "description": "Figma: color/button/primary/hover; mode Default."
+ },
+ {
+ "name": "--color-button-secondary",
+ "type": "color",
+ "value": "var(--ink-4)",
+ "description": "Figma: color/button/secondary; mode Default."
+ },
+ {
+ "name": "--color-button-secondary-hover",
+ "type": "color",
+ "value": "var(--ink-8)",
+ "description": "Figma: color/button/secondary/hover; mode Default."
+ },
+ {
+ "name": "--color-footer-link",
+ "type": "color",
+ "value": "var(--neutral-ink)",
+ "description": "Figma: color/footer/link; mode Default."
+ },
+ {
+ "name": "--color-announcement-border",
+ "type": "color",
+ "value": "var(--ink-8)",
+ "description": "Figma: color/announcement/border; mode Default."
+ },
+ {
+ "name": "--color-announcement-border-hover",
+ "type": "color",
+ "value": "var(--ink-15)",
+ "description": "Figma: color/announcement/border/hover; mode Default."
+ },
+ {
+ "name": "--color-focus",
+ "type": "color",
+ "value": "var(--ink-30)",
+ "description": "Figma: Archive/color/focus; mode Default."
+ },
+ {
+ "name": "--color-app-canvas",
+ "type": "color",
+ "value": "var(--color-background)",
+ "description": "White working canvas. Use glass selectively for layered controls and floating surfaces."
+ },
+ {
+ "name": "--color-app-text-primary",
+ "type": "color",
+ "value": "var(--ink-80)",
+ "description": "Soft graphite for primary app text. Shared neutral ink at 80%; preserves strong reading contrast without pure black."
+ },
+ {
+ "name": "--color-app-text-secondary",
+ "type": "color",
+ "value": "var(--color-text-secondary)",
+ "description": "App redesign proposal v0.1; shared landing aliases where applicable. No code implementation yet."
+ },
+ {
+ "name": "--color-app-text-muted",
+ "type": "color",
+ "value": "var(--color-nav-link-muted)",
+ "description": "App redesign proposal v0.1; shared landing aliases where applicable. No code implementation yet."
+ },
+ {
+ "name": "--color-app-glass",
+ "type": "color",
+ "value": "var(--color-glass)",
+ "description": "App redesign proposal v0.1; shared landing aliases where applicable. No code implementation yet."
+ },
+ {
+ "name": "--color-app-overlay",
+ "type": "color",
+ "value": "var(--color-glass-floating)",
+ "description": "App menus and popovers inherit shared floating glass."
+ },
+ {
+ "name": "--color-app-reading",
+ "type": "color",
+ "value": "rgb(255 255 255 / 96%)",
+ "description": "App redesign proposal v0.1; shared landing aliases where applicable. No code implementation yet."
+ },
+ {
+ "name": "--color-app-border",
+ "type": "color",
+ "value": "var(--color-border-subtle)",
+ "description": "App redesign proposal v0.1; shared landing aliases where applicable. No code implementation yet."
+ },
+ {
+ "name": "--color-app-hover",
+ "type": "color",
+ "value": "var(--color-icon-background-hover)",
+ "description": "App redesign proposal v0.1; shared landing aliases where applicable. No code implementation yet."
+ },
+ {
+ "name": "--color-app-selected",
+ "type": "color",
+ "value": "var(--black-8)",
+ "description": "App redesign proposal v0.1; shared landing aliases where applicable. No code implementation yet."
+ },
+ {
+ "name": "--color-app-pressed",
+ "type": "color",
+ "value": "var(--black-12)",
+ "description": "App redesign proposal v0.1; shared landing aliases where applicable. No code implementation yet."
+ },
+ {
+ "name": "--color-app-accent",
+ "type": "color",
+ "value": "var(--color-accent)",
+ "description": "App redesign proposal v0.1; shared landing aliases where applicable. No code implementation yet."
+ },
+ {
+ "name": "--color-app-error",
+ "type": "color",
+ "value": "#C83232",
+ "description": "App redesign proposal v0.1; shared landing aliases where applicable. No code implementation yet."
+ },
+ {
+ "name": "--color-app-success",
+ "type": "color",
+ "value": "var(--color-accent)",
+ "description": "App redesign proposal v0.1; shared landing aliases where applicable. No code implementation yet."
+ },
+ {
+ "name": "--color-chart-series",
+ "type": "color",
+ "value": "var(--ink-50)",
+ "description": "Quiet neutral graphite, 1.5px. Hover point uses the same pigment; tooltip text remains high contrast."
+ },
+ {
+ "name": "--color-chart-grid",
+ "type": "color",
+ "value": "var(--ink-4)",
+ "description": "Quiet neutral chart guides. Zero baseline remains present; grids are decorative."
+ },
+ {
+ "name": "--color-chart-area",
+ "type": "color",
+ "value": "var(--neutral-ink)",
+ "description": "Neutral pigment beneath the series at 6% opacity, fading to zero. A zero-data chart has no area fill."
+ },
+ {
+ "name": "--color-glass-floating",
+ "type": "color",
+ "value": "rgb(255 255 255 / 88%)",
+ "description": "Shared neutral white glass for floating menus across Landing and App. 20px blur; retain the solid fallback when backdrop blur is unavailable."
+ },
+ {
+ "name": "--ink-50",
+ "type": "color",
+ "value": "rgb(10 10 10 / 50%)",
+ "description": "Neutral medium gray for data series; never used for small body text."
+ },
+ {
+ "name": "--color-landing-nav-dropdown",
+ "type": "color",
+ "value": "#FAFAFC",
+ "description": "Landing implementation: --color-nav-dropdown (neutral-pearl). Scoped here so App design tokens are unaffected."
+ },
+ {
+ "name": "--color-landing-nav-backdrop",
+ "type": "color",
+ "value": "rgb(232 232 237 / 40%)",
+ "description": "Landing implementation: --color-nav-backdrop; 20px backdrop blur."
+ },
+ {
+ "name": "--color-landing-button-secondary",
+ "type": "color",
+ "value": "var(--warm-3)",
+ "description": "Landing implementation: --color-button-secondary. App secondary controls retain their own token."
+ },
+ {
+ "name": "--color-mark-edge-start",
+ "type": "color",
+ "value": "#858585",
+ "description": "Isometric product-mark outline start; sampled from existing feature-mark source SVG."
+ },
+ {
+ "name": "--color-mark-edge-end",
+ "type": "color",
+ "value": "#D9D9D9",
+ "description": "Isometric product-mark outline end; sampled from existing feature-mark source SVG."
+ },
+ {
+ "name": "--color-mark-face-top",
+ "type": "color",
+ "value": "#F7F7F7",
+ "description": "Existing feature-mark upper face."
+ },
+ {
+ "name": "--color-mark-face-front",
+ "type": "color",
+ "value": "#EFEFEF",
+ "description": "Existing feature-mark front face."
+ },
+ {
+ "name": "--color-mark-face-side",
+ "type": "color",
+ "value": "#E5E5E5",
+ "description": "Existing feature-mark side face."
+ },
+ {
+ "name": "--color-tooltip-surface",
+ "type": "color",
+ "value": "#191919",
+ "description": "All explanatory tooltips use dark neutral. Jump to latest is the same tooltip. Light glass is for buttons, menus, dialogs, and richer data popovers."
+ },
+ {
+ "name": "--color-tooltip-text",
+ "type": "color",
+ "value": "#FFFFFF",
+ "description": "Shared tooltip foreground."
+ },
+ {
+ "name": "--color-auth-panel",
+ "type": "color",
+ "value": "#00000000",
+ "description": "Auth forms sit directly on the white page. Glass is reserved for opened menus and popovers."
+ },
+ {
+ "name": "--color-auth-field",
+ "type": "color",
+ "value": "var(--color-app-reading)",
+ "description": "Auth proposal: readable fields use the app 96% white reading surface."
+ },
+ {
+ "name": "--color-docs-green",
+ "type": "color",
+ "value": "#15803D",
+ "description": "Docs semantic green ink from Sim platform badge tokens; scope to method, status and syntax labels."
+ },
+ {
+ "name": "--color-docs-green-soft",
+ "type": "color",
+ "value": "rgb(187 247 208 / 35%)",
+ "description": "Compact green badge fill from Sim platform. Keep page and glass surfaces neutral."
+ },
+ {
+ "name": "--color-docs-purple",
+ "type": "color",
+ "value": "#7C3AED",
+ "description": "Docs semantic purple ink from Sim platform badge tokens; scope to method, status and syntax labels."
+ },
+ {
+ "name": "--color-docs-purple-soft",
+ "type": "color",
+ "value": "rgb(233 213 255 / 35%)",
+ "description": "Compact purple badge fill from Sim platform. Keep page and glass surfaces neutral."
+ },
+ {
+ "name": "--color-docs-orange",
+ "type": "color",
+ "value": "#C2410C",
+ "description": "Docs semantic orange ink from Sim platform badge tokens; scope to method, status and syntax labels."
+ },
+ {
+ "name": "--color-docs-orange-soft",
+ "type": "color",
+ "value": "rgb(254 215 170 / 35%)",
+ "description": "Compact orange badge fill from Sim platform. Keep page and glass surfaces neutral."
+ },
+ {
+ "name": "--color-docs-red",
+ "type": "color",
+ "value": "#C83232",
+ "description": "Error text color."
+ },
+ {
+ "name": "--color-docs-red-soft",
+ "type": "color",
+ "value": "rgb(254 202 202 / 35%)",
+ "description": "Compact red badge fill from Sim platform. Keep page and glass surfaces neutral."
+ },
+ {
+ "name": "--color-docs-blue",
+ "type": "color",
+ "value": "var(--color-accent)",
+ "description": "Reuse Sim link and action blue."
+ },
+ {
+ "name": "--color-docs-blue-soft",
+ "type": "color",
+ "value": "rgb(191 219 254 / 35%)",
+ "description": "Compact blue badge fill from Sim platform. Keep page and glass surfaces neutral."
+ },
+ {
+ "name": "--color-docs-gray",
+ "type": "color",
+ "value": "var(--color-app-text-secondary)",
+ "description": "Docs semantic gray ink from Sim platform badge tokens; scope to method, status and syntax labels."
+ },
+ {
+ "name": "--color-docs-gray-soft",
+ "type": "color",
+ "value": "var(--color-surface-muted)",
+ "description": "Compact gray badge fill from Sim platform. Keep page and glass surfaces neutral."
+ },
+ {
+ "name": "--color-docs-dark-canvas",
+ "type": "color",
+ "value": "#1B1B1B",
+ "description": "Dark docs specimen: shared Sim platform .dark token value from globals.css."
+ },
+ {
+ "name": "--color-docs-dark-reading",
+ "type": "color",
+ "value": "#1F1F1F",
+ "description": "Dark docs specimen: shared Sim platform .dark token value from globals.css."
+ },
+ {
+ "name": "--color-docs-dark-text",
+ "type": "color",
+ "value": "#E6E6E6",
+ "description": "Dark docs specimen: shared Sim platform .dark token value from globals.css."
+ },
+ {
+ "name": "--color-docs-dark-muted",
+ "type": "color",
+ "value": "#CCCCCC",
+ "description": "Dark docs specimen: shared Sim platform .dark token value from globals.css."
+ },
+ {
+ "name": "--color-docs-dark-border",
+ "type": "color",
+ "value": "#444444",
+ "description": "Dark docs specimen: shared Sim platform .dark token value from globals.css."
+ },
+ {
+ "name": "--color-docs-dark-hover",
+ "type": "color",
+ "value": "#262626",
+ "description": "Dark docs specimen: shared Sim platform .dark token value from globals.css."
+ },
+ {
+ "name": "--color-docs-dark-selected",
+ "type": "color",
+ "value": "#2C2C2C",
+ "description": "Dark docs specimen: shared Sim platform .dark token value from globals.css."
+ },
+ {
+ "name": "--color-docs-dark-blue",
+ "type": "color",
+ "value": "#93C5FD",
+ "description": "Dark docs specimen: shared Sim platform .dark token value from globals.css."
+ },
+ {
+ "name": "--color-docs-dark-green",
+ "type": "color",
+ "value": "#86EFAC",
+ "description": "Dark docs specimen: shared Sim platform .dark token value from globals.css."
+ },
+ {
+ "name": "--color-docs-dark-purple",
+ "type": "color",
+ "value": "#D8B4FE",
+ "description": "Dark docs specimen: shared Sim platform .dark token value from globals.css."
+ },
+ {
+ "name": "--color-docs-dark-orange",
+ "type": "color",
+ "value": "#FDBA74",
+ "description": "Dark docs specimen: shared Sim platform .dark token value from globals.css."
+ },
+ {
+ "name": "--color-docs-dark-red",
+ "type": "color",
+ "value": "#FCA5A5",
+ "description": "Dark docs specimen: shared Sim platform .dark token value from globals.css."
+ },
+ {
+ "name": "--color-surface-reading",
+ "type": "color",
+ "value": "var(--color-app-reading)",
+ "description": "Landing reading surfaces use the established 96% white app material."
+ },
+ {
+ "name": "--color-status-complete",
+ "type": "color",
+ "value": "var(--color-accent)"
+ },
+ {
+ "name": "--color-status-complete-background",
+ "type": "color",
+ "value": "rgb(235 243 251)"
+ },
+ {
+ "name": "--color-status-pending",
+ "type": "color",
+ "value": "#856404",
+ "description": "Pending approval text and clock icon; readable dark yellow on the pending surface."
+ },
+ {
+ "name": "--color-status-pending-background",
+ "type": "color",
+ "value": "#FFF5CC",
+ "description": "Pale yellow surface for a static Pending approval status. Landing inspection tickets."
+ },
+ {
+ "name": "--font-sans",
+ "type": "fontFamily",
+ "value": "\"Geist\"",
+ "description": "Source: Sim Figma variable font/sans; all modes use Geist."
+ },
+ {
+ "name": "--font-sans-mobile",
+ "type": "fontFamily",
+ "value": "\"Geist\"",
+ "description": "Figma: font/sans; mode Mobile."
+ },
+ {
+ "name": "--font-sans-tablet",
+ "type": "fontFamily",
+ "value": "\"Geist\"",
+ "description": "Figma: font/sans; mode Tablet."
+ },
+ {
+ "name": "--font-mono",
+ "type": "fontFamily",
+ "value": "\"Geist Mono\"",
+ "description": "Figma: font/mono; mode Desktop."
+ },
+ {
+ "name": "--font-mono-mobile",
+ "type": "fontFamily",
+ "value": "\"Geist Mono\"",
+ "description": "Figma: font/mono; mode Mobile."
+ },
+ {
+ "name": "--font-mono-tablet",
+ "type": "fontFamily",
+ "value": "\"Geist Mono\"",
+ "description": "Figma: font/mono; mode Tablet."
+ },
+ {
+ "name": "--type-display-size",
+ "type": "fontSize",
+ "value": "60px",
+ "description": "Figma: type/display/size; mode Desktop."
+ },
+ {
+ "name": "--type-display-size-mobile",
+ "type": "fontSize",
+ "value": "36px",
+ "description": "Figma: type/display/size; mode Mobile."
+ },
+ {
+ "name": "--type-display-size-tablet",
+ "type": "fontSize",
+ "value": "48px",
+ "description": "Figma: type/display/size; mode Tablet."
+ },
+ {
+ "name": "--type-page-size",
+ "type": "fontSize",
+ "value": "48px",
+ "description": "Figma: type/page/size; mode Desktop."
+ },
+ {
+ "name": "--type-page-size-mobile",
+ "type": "fontSize",
+ "value": "36px",
+ "description": "Figma: type/page/size; mode Mobile."
+ },
+ {
+ "name": "--type-page-size-tablet",
+ "type": "fontSize",
+ "value": "48px",
+ "description": "Figma: type/page/size; mode Tablet."
+ },
+ {
+ "name": "--type-section-size",
+ "type": "fontSize",
+ "value": "30px",
+ "description": "Figma: type/section/size; mode Desktop."
+ },
+ {
+ "name": "--type-section-size-mobile",
+ "type": "fontSize",
+ "value": "24px",
+ "description": "Figma: type/section/size; mode Mobile."
+ },
+ {
+ "name": "--type-section-size-tablet",
+ "type": "fontSize",
+ "value": "30px",
+ "description": "Figma: type/section/size; mode Tablet."
+ },
+ {
+ "name": "--type-subsection-size",
+ "type": "fontSize",
+ "value": "24px",
+ "description": "Figma: type/subsection/size; mode Desktop."
+ },
+ {
+ "name": "--type-subsection-size-mobile",
+ "type": "fontSize",
+ "value": "24px",
+ "description": "Figma: type/subsection/size; mode Mobile."
+ },
+ {
+ "name": "--type-subsection-size-tablet",
+ "type": "fontSize",
+ "value": "24px",
+ "description": "Figma: type/subsection/size; mode Tablet."
+ },
+ {
+ "name": "--type-card-size",
+ "type": "fontSize",
+ "value": "16px",
+ "description": "Figma: type/card/size; mode Desktop."
+ },
+ {
+ "name": "--type-card-size-mobile",
+ "type": "fontSize",
+ "value": "16px",
+ "description": "Figma: type/card/size; mode Mobile."
+ },
+ {
+ "name": "--type-card-size-tablet",
+ "type": "fontSize",
+ "value": "16px",
+ "description": "Figma: type/card/size; mode Tablet."
+ },
+ {
+ "name": "--type-lead-size",
+ "type": "fontSize",
+ "value": "18px",
+ "description": "Figma: type/lead/size; mode Desktop."
+ },
+ {
+ "name": "--type-lead-size-mobile",
+ "type": "fontSize",
+ "value": "18px",
+ "description": "Figma: type/lead/size; mode Mobile."
+ },
+ {
+ "name": "--type-lead-size-tablet",
+ "type": "fontSize",
+ "value": "18px",
+ "description": "Figma: type/lead/size; mode Tablet."
+ },
+ {
+ "name": "--type-body-size",
+ "type": "fontSize",
+ "value": "16px",
+ "description": "Figma: type/body/size; mode Desktop."
+ },
+ {
+ "name": "--type-body-size-mobile",
+ "type": "fontSize",
+ "value": "16px",
+ "description": "Figma: type/body/size; mode Mobile."
+ },
+ {
+ "name": "--type-body-size-tablet",
+ "type": "fontSize",
+ "value": "16px",
+ "description": "Figma: type/body/size; mode Tablet."
+ },
+ {
+ "name": "--type-body-small-size",
+ "type": "fontSize",
+ "value": "14px",
+ "description": "Figma: type/body-small/size; mode Desktop."
+ },
+ {
+ "name": "--type-body-small-size-mobile",
+ "type": "fontSize",
+ "value": "14px",
+ "description": "Figma: type/body-small/size; mode Mobile."
+ },
+ {
+ "name": "--type-body-small-size-tablet",
+ "type": "fontSize",
+ "value": "14px",
+ "description": "Figma: type/body-small/size; mode Tablet."
+ },
+ {
+ "name": "--type-caption-size",
+ "type": "fontSize",
+ "value": "12px",
+ "description": "Figma: type/caption/size; mode Desktop."
+ },
+ {
+ "name": "--type-caption-size-mobile",
+ "type": "fontSize",
+ "value": "12px",
+ "description": "Figma: type/caption/size; mode Mobile."
+ },
+ {
+ "name": "--type-caption-size-tablet",
+ "type": "fontSize",
+ "value": "12px",
+ "description": "Figma: type/caption/size; mode Tablet."
+ },
+ {
+ "name": "--type-nav-size",
+ "type": "fontSize",
+ "value": "13px",
+ "description": "Figma: type/nav/size; mode Desktop."
+ },
+ {
+ "name": "--type-nav-size-mobile",
+ "type": "fontSize",
+ "value": "13px",
+ "description": "Figma: type/nav/size; mode Mobile."
+ },
+ {
+ "name": "--type-nav-size-tablet",
+ "type": "fontSize",
+ "value": "13px",
+ "description": "Figma: type/nav/size; mode Tablet."
+ },
+ {
+ "name": "--type-button-size",
+ "type": "fontSize",
+ "value": "14px",
+ "description": "Figma: type/button/size; mode Desktop."
+ },
+ {
+ "name": "--type-button-size-mobile",
+ "type": "fontSize",
+ "value": "14px",
+ "description": "Figma: type/button/size; mode Mobile."
+ },
+ {
+ "name": "--type-button-size-tablet",
+ "type": "fontSize",
+ "value": "14px",
+ "description": "Figma: type/button/size; mode Tablet."
+ },
+ {
+ "name": "--type-eyebrow-size",
+ "type": "fontSize",
+ "value": "14px",
+ "description": "Figma: type/eyebrow/size; mode Desktop."
+ },
+ {
+ "name": "--type-eyebrow-size-mobile",
+ "type": "fontSize",
+ "value": "14px",
+ "description": "Figma: type/eyebrow/size; mode Mobile."
+ },
+ {
+ "name": "--type-eyebrow-size-tablet",
+ "type": "fontSize",
+ "value": "14px",
+ "description": "Figma: type/eyebrow/size; mode Tablet."
+ },
+ {
+ "name": "--type-code-size",
+ "type": "fontSize",
+ "value": "13px",
+ "description": "Figma: type/code/size; mode Desktop."
+ },
+ {
+ "name": "--type-code-size-mobile",
+ "type": "fontSize",
+ "value": "13px",
+ "description": "Figma: type/code/size; mode Mobile."
+ },
+ {
+ "name": "--type-code-size-tablet",
+ "type": "fontSize",
+ "value": "13px",
+ "description": "Figma: type/code/size; mode Tablet."
+ },
+ {
+ "name": "--type-footer-size",
+ "type": "fontSize",
+ "value": "13px",
+ "description": "Figma: type/footer/size; mode Default."
+ },
+ {
+ "name": "--type-badge-size",
+ "type": "fontSize",
+ "value": "11px",
+ "description": "Figma: type/badge/size; mode Default."
+ },
+ {
+ "name": "--type-hero-size",
+ "type": "fontSize",
+ "value": "40px",
+ "description": "Platform hero headline, desktop and tablet. Separate from the 60px display role."
+ },
+ {
+ "name": "--type-hero-size-mobile",
+ "type": "fontSize",
+ "value": "32px"
+ },
+ {
+ "name": "--type-button-large-size",
+ "type": "fontSize",
+ "value": "16px",
+ "description": "Large marketing CTA label. Retains 44px button height with 20px line-height and 12px vertical padding."
+ },
+ {
+ "name": "--text-app-page",
+ "type": "fontSize",
+ "value": "24px",
+ "description": "App redesign proposal v0.1; shared landing aliases where applicable. No code implementation yet."
+ },
+ {
+ "name": "--text-app-section",
+ "type": "fontSize",
+ "value": "16px",
+ "description": "App redesign proposal v0.1; shared landing aliases where applicable. No code implementation yet."
+ },
+ {
+ "name": "--text-app-body",
+ "type": "fontSize",
+ "value": "14px",
+ "description": "App redesign proposal v0.1; shared landing aliases where applicable. No code implementation yet."
+ },
+ {
+ "name": "--text-app-control",
+ "type": "fontSize",
+ "value": "13px",
+ "description": "App redesign proposal v0.1; shared landing aliases where applicable. No code implementation yet."
+ },
+ {
+ "name": "--text-app-caption",
+ "type": "fontSize",
+ "value": "12px",
+ "description": "App redesign proposal v0.1; shared landing aliases where applicable. No code implementation yet."
+ },
+ {
+ "name": "--text-chart-value",
+ "type": "fontSize",
+ "value": "var(--text-app-page)",
+ "description": "Use the app's 24px metric/page size, Geist Medium with tabular figures."
+ },
+ {
+ "name": "--text-docs-title",
+ "type": "fontSize",
+ "value": "32px"
+ },
+ {
+ "name": "--text-docs-body",
+ "type": "fontSize",
+ "value": "var(--type-body-size)"
+ },
+ {
+ "name": "--font-weight-regular",
+ "type": "fontWeight",
+ "value": "400",
+ "description": "Figma: weight/regular; mode Desktop."
+ },
+ {
+ "name": "--font-weight-regular-mobile",
+ "type": "fontWeight",
+ "value": "400",
+ "description": "Figma: weight/regular; mode Mobile."
+ },
+ {
+ "name": "--font-weight-regular-tablet",
+ "type": "fontWeight",
+ "value": "400",
+ "description": "Figma: weight/regular; mode Tablet."
+ },
+ {
+ "name": "--font-weight-medium",
+ "type": "fontWeight",
+ "value": "500",
+ "description": "Figma: weight/medium; mode Desktop."
+ },
+ {
+ "name": "--font-weight-medium-mobile",
+ "type": "fontWeight",
+ "value": "500",
+ "description": "Figma: weight/medium; mode Mobile."
+ },
+ {
+ "name": "--font-weight-medium-tablet",
+ "type": "fontWeight",
+ "value": "500",
+ "description": "Figma: weight/medium; mode Tablet."
+ },
+ {
+ "name": "--font-weight-semibold",
+ "type": "fontWeight",
+ "value": "600",
+ "description": "Figma: weight/semibold; mode Desktop."
+ },
+ {
+ "name": "--font-weight-semibold-mobile",
+ "type": "fontWeight",
+ "value": "600",
+ "description": "Figma: weight/semibold; mode Mobile."
+ },
+ {
+ "name": "--font-weight-semibold-tablet",
+ "type": "fontWeight",
+ "value": "600",
+ "description": "Figma: weight/semibold; mode Tablet."
+ },
+ {
+ "name": "--font-weight-display",
+ "type": "fontWeight",
+ "value": "450",
+ "description": "Figma: weight/display; mode Desktop."
+ },
+ {
+ "name": "--font-weight-display-mobile",
+ "type": "fontWeight",
+ "value": "450",
+ "description": "Figma: weight/display; mode Mobile."
+ },
+ {
+ "name": "--font-weight-display-tablet",
+ "type": "fontWeight",
+ "value": "450",
+ "description": "Figma: weight/display; mode Tablet."
+ },
+ {
+ "name": "--font-weight-control",
+ "type": "fontWeight",
+ "value": "450",
+ "description": "Geist optical compensation for X VF 500 at control sizes. Numeric weights are not visually equivalent between these families. Figma: weight/control; mode Desktop."
+ },
+ {
+ "name": "--font-weight-control-mobile",
+ "type": "fontWeight",
+ "value": "450",
+ "description": "Geist optical compensation for X VF 500 at control sizes. Numeric weights are not visually equivalent between these families. Figma: weight/control; mode Mobile."
+ },
+ {
+ "name": "--font-weight-control-tablet",
+ "type": "fontWeight",
+ "value": "450",
+ "description": "Geist optical compensation for X VF 500 at control sizes. Numeric weights are not visually equivalent between these families. Figma: weight/control; mode Tablet."
+ },
+ {
+ "name": "--type-footer-tracking",
+ "type": "letterSpacing",
+ "value": "0px",
+ "description": "Figma: type/footer/tracking; mode Default."
+ },
+ {
+ "name": "--type-hero-tracking",
+ "type": "letterSpacing",
+ "value": "-0.01em"
+ },
+ {
+ "name": "--tracking-app-title",
+ "type": "letterSpacing",
+ "value": "-0.02em",
+ "description": "App redesign proposal v0.1; shared landing aliases where applicable. No code implementation yet."
+ },
+ {
+ "name": "--tracking-app-body",
+ "type": "letterSpacing",
+ "value": "0em",
+ "description": "App redesign proposal v0.1; shared landing aliases where applicable. No code implementation yet."
+ },
+ {
+ "name": "--type-display-leading",
+ "type": "lineHeight",
+ "value": "60px",
+ "description": "Figma: type/display/leading; mode Desktop."
+ },
+ {
+ "name": "--type-display-leading-mobile",
+ "type": "lineHeight",
+ "value": "39.599998474121094px",
+ "description": "Figma: type/display/leading; mode Mobile."
+ },
+ {
+ "name": "--type-display-leading-tablet",
+ "type": "lineHeight",
+ "value": "48px",
+ "description": "Figma: type/display/leading; mode Tablet."
+ },
+ {
+ "name": "--type-page-leading",
+ "type": "lineHeight",
+ "value": "48px",
+ "description": "Figma: type/page/leading; mode Desktop."
+ },
+ {
+ "name": "--type-page-leading-mobile",
+ "type": "lineHeight",
+ "value": "40px",
+ "description": "Figma: type/page/leading; mode Mobile."
+ },
+ {
+ "name": "--type-page-leading-tablet",
+ "type": "lineHeight",
+ "value": "48px",
+ "description": "Figma: type/page/leading; mode Tablet."
+ },
+ {
+ "name": "--type-section-leading",
+ "type": "lineHeight",
+ "value": "36px",
+ "description": "Figma: type/section/leading; mode Desktop."
+ },
+ {
+ "name": "--type-section-leading-mobile",
+ "type": "lineHeight",
+ "value": "32px",
+ "description": "Figma: type/section/leading; mode Mobile."
+ },
+ {
+ "name": "--type-section-leading-tablet",
+ "type": "lineHeight",
+ "value": "36px",
+ "description": "Figma: type/section/leading; mode Tablet."
+ },
+ {
+ "name": "--type-subsection-leading",
+ "type": "lineHeight",
+ "value": "32px",
+ "description": "Figma: type/subsection/leading; mode Desktop."
+ },
+ {
+ "name": "--type-subsection-leading-mobile",
+ "type": "lineHeight",
+ "value": "32px",
+ "description": "Figma: type/subsection/leading; mode Mobile."
+ },
+ {
+ "name": "--type-subsection-leading-tablet",
+ "type": "lineHeight",
+ "value": "32px",
+ "description": "Figma: type/subsection/leading; mode Tablet."
+ },
+ {
+ "name": "--type-card-leading",
+ "type": "lineHeight",
+ "value": "22px",
+ "description": "Figma: type/card/leading; mode Desktop."
+ },
+ {
+ "name": "--type-card-leading-mobile",
+ "type": "lineHeight",
+ "value": "22px",
+ "description": "Figma: type/card/leading; mode Mobile."
+ },
+ {
+ "name": "--type-card-leading-tablet",
+ "type": "lineHeight",
+ "value": "22px",
+ "description": "Figma: type/card/leading; mode Tablet."
+ },
+ {
+ "name": "--type-lead-leading",
+ "type": "lineHeight",
+ "value": "29.25px",
+ "description": "Figma: type/lead/leading; mode Desktop."
+ },
+ {
+ "name": "--type-lead-leading-mobile",
+ "type": "lineHeight",
+ "value": "29.25px",
+ "description": "Figma: type/lead/leading; mode Mobile."
+ },
+ {
+ "name": "--type-lead-leading-tablet",
+ "type": "lineHeight",
+ "value": "29.25px",
+ "description": "Figma: type/lead/leading; mode Tablet."
+ },
+ {
+ "name": "--type-body-leading",
+ "type": "lineHeight",
+ "value": "26px",
+ "description": "Figma: type/body/leading; mode Desktop."
+ },
+ {
+ "name": "--type-body-leading-mobile",
+ "type": "lineHeight",
+ "value": "26px",
+ "description": "Figma: type/body/leading; mode Mobile."
+ },
+ {
+ "name": "--type-body-leading-tablet",
+ "type": "lineHeight",
+ "value": "26px",
+ "description": "Figma: type/body/leading; mode Tablet."
+ },
+ {
+ "name": "--type-body-small-leading",
+ "type": "lineHeight",
+ "value": "22.75px",
+ "description": "Figma: type/body-small/leading; mode Desktop."
+ },
+ {
+ "name": "--type-body-small-leading-mobile",
+ "type": "lineHeight",
+ "value": "22.75px",
+ "description": "Figma: type/body-small/leading; mode Mobile."
+ },
+ {
+ "name": "--type-body-small-leading-tablet",
+ "type": "lineHeight",
+ "value": "22.75px",
+ "description": "Figma: type/body-small/leading; mode Tablet."
+ },
+ {
+ "name": "--type-caption-leading",
+ "type": "lineHeight",
+ "value": "16px",
+ "description": "Figma: type/caption/leading; mode Desktop."
+ },
+ {
+ "name": "--type-caption-leading-mobile",
+ "type": "lineHeight",
+ "value": "16px",
+ "description": "Figma: type/caption/leading; mode Mobile."
+ },
+ {
+ "name": "--type-caption-leading-tablet",
+ "type": "lineHeight",
+ "value": "16px",
+ "description": "Figma: type/caption/leading; mode Tablet."
+ },
+ {
+ "name": "--type-nav-leading",
+ "type": "lineHeight",
+ "value": "19.5px",
+ "description": "Figma: type/nav/leading; mode Desktop."
+ },
+ {
+ "name": "--type-nav-leading-mobile",
+ "type": "lineHeight",
+ "value": "19.5px",
+ "description": "Figma: type/nav/leading; mode Mobile."
+ },
+ {
+ "name": "--type-nav-leading-tablet",
+ "type": "lineHeight",
+ "value": "19.5px",
+ "description": "Figma: type/nav/leading; mode Tablet."
+ },
+ {
+ "name": "--type-button-leading",
+ "type": "lineHeight",
+ "value": "20px",
+ "description": "Figma: type/button/leading; mode Desktop."
+ },
+ {
+ "name": "--type-button-leading-mobile",
+ "type": "lineHeight",
+ "value": "20px",
+ "description": "Figma: type/button/leading; mode Mobile."
+ },
+ {
+ "name": "--type-button-leading-tablet",
+ "type": "lineHeight",
+ "value": "20px",
+ "description": "Figma: type/button/leading; mode Tablet."
+ },
+ {
+ "name": "--type-eyebrow-leading",
+ "type": "lineHeight",
+ "value": "20px",
+ "description": "Figma: type/eyebrow/leading; mode Desktop."
+ },
+ {
+ "name": "--type-eyebrow-leading-mobile",
+ "type": "lineHeight",
+ "value": "20px",
+ "description": "Figma: type/eyebrow/leading; mode Mobile."
+ },
+ {
+ "name": "--type-eyebrow-leading-tablet",
+ "type": "lineHeight",
+ "value": "20px",
+ "description": "Figma: type/eyebrow/leading; mode Tablet."
+ },
+ {
+ "name": "--type-code-leading",
+ "type": "lineHeight",
+ "value": "19.5px",
+ "description": "Figma: type/code/leading; mode Desktop."
+ },
+ {
+ "name": "--type-code-leading-mobile",
+ "type": "lineHeight",
+ "value": "19.5px",
+ "description": "Figma: type/code/leading; mode Mobile."
+ },
+ {
+ "name": "--type-code-leading-tablet",
+ "type": "lineHeight",
+ "value": "19.5px",
+ "description": "Figma: type/code/leading; mode Tablet."
+ },
+ {
+ "name": "--type-footer-leading",
+ "type": "lineHeight",
+ "value": "21.125px",
+ "description": "Figma: type/footer/leading; mode Default."
+ },
+ {
+ "name": "--type-badge-leading",
+ "type": "lineHeight",
+ "value": "11px",
+ "description": "Figma: type/badge/leading; mode Default."
+ },
+ {
+ "name": "--type-hero-leading",
+ "type": "lineHeight",
+ "value": "44px"
+ },
+ {
+ "name": "--type-hero-leading-mobile",
+ "type": "lineHeight",
+ "value": "36px"
+ },
+ {
+ "name": "--leading-app-page",
+ "type": "lineHeight",
+ "value": "32px",
+ "description": "App redesign proposal v0.1; shared landing aliases where applicable. No code implementation yet."
+ },
+ {
+ "name": "--leading-app-body",
+ "type": "lineHeight",
+ "value": "22px",
+ "description": "Body / readable prose, 14px on 22px."
+ },
+ {
+ "name": "--leading-app-control",
+ "type": "lineHeight",
+ "value": "18px",
+ "description": "App redesign proposal v0.1; shared landing aliases where applicable. No code implementation yet."
+ },
+ {
+ "name": "--leading-docs-title",
+ "type": "lineHeight",
+ "value": "40px"
+ },
+ {
+ "name": "--leading-docs-body",
+ "type": "lineHeight",
+ "value": "var(--type-body-leading)"
+ },
+ {
+ "name": "--opacity-app-disabled",
+ "type": "opacity",
+ "value": "40%",
+ "description": "App redesign proposal v0.1; shared landing aliases where applicable. No code implementation yet."
+ },
+ {
+ "name": "--opacity-chart-area",
+ "type": "opacity",
+ "value": "6%",
+ "description": "A restrained translucent neutral fade beneath a populated series. Zero data has no fill."
+ },
+ {
+ "name": "--breakpoint-app-compact",
+ "type": "breakpoint",
+ "value": "768px",
+ "description": "App redesign proposal v0.1; shared landing aliases where applicable. No code implementation yet."
+ },
+ {
+ "name": "--breakpoint-app-expanded",
+ "type": "breakpoint",
+ "value": "1280px",
+ "description": "280px expanded sidebar at 1280px and above. Below, use glass header and a navigation drawer."
+ },
+ {
+ "name": "--size-button-icon",
+ "type": "container",
+ "value": "16px",
+ "description": "16px from 640px; 20px below 640px. Standard button trailing icon. Figma: size/button-icon; mode Default."
+ },
+ {
+ "name": "--size-button-icon-large",
+ "type": "container",
+ "value": "20px",
+ "description": "Figma: size/button-icon-large; mode Default."
+ },
+ {
+ "name": "--height-nav",
+ "type": "container",
+ "value": "56px",
+ "description": "Figma: height/nav; mode Default."
+ },
+ {
+ "name": "--width-nav-logo",
+ "type": "container",
+ "value": "40px",
+ "description": "Figma: width/nav-logo; mode Default."
+ },
+ {
+ "name": "--container-app-sidebar",
+ "type": "container",
+ "value": "280px",
+ "description": "Baseline expanded sidebar width across web, desktop, settings, and shared shell specimens."
+ },
+ {
+ "name": "--container-app-composer",
+ "type": "container",
+ "value": "688px",
+ "description": "688px documentation specimen only. Application composer width is 100% of the Home content container."
+ },
+ {
+ "name": "--container-app-settings",
+ "type": "container",
+ "value": "700px",
+ "description": "App redesign proposal v0.1; shared landing aliases where applicable. No code implementation yet."
+ },
+ {
+ "name": "--container-app-command",
+ "type": "container",
+ "value": "680px",
+ "description": "App redesign proposal v0.1; shared landing aliases where applicable. No code implementation yet."
+ },
+ {
+ "name": "--container-app-modal-sm",
+ "type": "container",
+ "value": "440px",
+ "description": "Quick decisions and single-field forms."
+ },
+ {
+ "name": "--container-app-modal-md",
+ "type": "container",
+ "value": "560px",
+ "description": "Default task modal."
+ },
+ {
+ "name": "--container-app-modal-lg",
+ "type": "container",
+ "value": "760px",
+ "description": "Detailed reviews and related configuration."
+ },
+ {
+ "name": "--container-app-dashboard",
+ "type": "container",
+ "value": "1200px",
+ "description": "Maximum Home content width. Fill main area with 48px gutters; composer always fills this container. Equal two columns above 900px available content; stack below."
+ },
+ {
+ "name": "--container-chat-thread",
+ "type": "container",
+ "value": "800px",
+ "description": "Shared transcript and composer width; centered with 48px minimum desktop gutters."
+ },
+ {
+ "name": "--container-chat-message",
+ "type": "container",
+ "value": "640px",
+ "description": "Maximum user bubble width; right aligned within thread."
+ },
+ {
+ "name": "--container-auth-form",
+ "type": "container",
+ "value": "400px",
+ "description": "Auth composition uses a 400px reading measure with the App field and typography system. On mobile, use available width minus 32px."
+ },
+ {
+ "name": "--container-auth-panel",
+ "type": "container",
+ "value": "var(--container-auth-form)",
+ "description": "Compatibility alias for the open auth form column. 400px maximum width, no outer padding or panel chrome."
+ },
+ {
+ "name": "--container-auth-nav",
+ "type": "container",
+ "value": "1280px",
+ "description": "Landing content width; 16px inner inset places logo at x=96 on 1440px canvas."
+ },
+ {
+ "name": "--container-docs-toc",
+ "type": "container",
+ "value": "208px"
+ },
+ {
+ "name": "--container-docs-sidebar",
+ "type": "container",
+ "value": "216px"
+ },
+ {
+ "name": "--container-docs-article",
+ "type": "container",
+ "value": "680px"
+ },
+ {
+ "name": "--container-docs-shell",
+ "type": "container",
+ "value": "1280px"
+ },
+ {
+ "name": "--space-0",
+ "type": "spacing",
+ "value": "0px",
+ "description": "Figma: space/0; mode Default."
+ },
+ {
+ "name": "--space-3",
+ "type": "spacing",
+ "value": "3px",
+ "description": "Figma: space/3; mode Default."
+ },
+ {
+ "name": "--space-4",
+ "type": "spacing",
+ "value": "4px",
+ "description": "Figma: space/4; mode Default."
+ },
+ {
+ "name": "--space-6",
+ "type": "spacing",
+ "value": "6px",
+ "description": "Figma: space/6; mode Default."
+ },
+ {
+ "name": "--space-8",
+ "type": "spacing",
+ "value": "8px",
+ "description": "Figma: space/8; mode Default."
+ },
+ {
+ "name": "--space-10",
+ "type": "spacing",
+ "value": "10px",
+ "description": "Figma: space/10; mode Default."
+ },
+ {
+ "name": "--space-12",
+ "type": "spacing",
+ "value": "12px",
+ "description": "Figma: space/12; mode Default."
+ },
+ {
+ "name": "--space-16",
+ "type": "spacing",
+ "value": "16px",
+ "description": "Figma: space/16; mode Default."
+ },
+ {
+ "name": "--space-18",
+ "type": "spacing",
+ "value": "18px",
+ "description": "Figma: space/18; mode Default."
+ },
+ {
+ "name": "--space-20",
+ "type": "spacing",
+ "value": "20px",
+ "description": "Figma: space/20; mode Default."
+ },
+ {
+ "name": "--stroke-border",
+ "type": "spacing",
+ "value": "1px",
+ "description": "Figma: stroke/border; mode Default."
+ },
+ {
+ "name": "--space-24",
+ "type": "spacing",
+ "value": "24px",
+ "description": "Figma: space/24; mode Default."
+ },
+ {
+ "name": "--space-32",
+ "type": "spacing",
+ "value": "32px",
+ "description": "Figma: space/32; mode Default."
+ },
+ {
+ "name": "--space-40",
+ "type": "spacing",
+ "value": "40px",
+ "description": "Figma: space/40; mode Default."
+ },
+ {
+ "name": "--space-48",
+ "type": "spacing",
+ "value": "48px",
+ "description": "Figma: space/48; mode Default."
+ },
+ {
+ "name": "--space-56",
+ "type": "spacing",
+ "value": "56px",
+ "description": "Figma: space/56; mode Default."
+ },
+ {
+ "name": "--space-64",
+ "type": "spacing",
+ "value": "64px",
+ "description": "Figma: space/64; mode Default."
+ },
+ {
+ "name": "--spacing-app-unit",
+ "type": "spacing",
+ "value": "4px",
+ "description": "App redesign proposal v0.1; shared landing aliases where applicable. No code implementation yet."
+ },
+ {
+ "name": "--spacing-app-control-md",
+ "type": "spacing",
+ "value": "32px",
+ "description": "App redesign proposal v0.1; shared landing aliases where applicable. No code implementation yet."
+ },
+ {
+ "name": "--spacing-app-control-sm",
+ "type": "spacing",
+ "value": "28px",
+ "description": "App redesign proposal v0.1; shared landing aliases where applicable. No code implementation yet."
+ },
+ {
+ "name": "--spacing-app-chip",
+ "type": "spacing",
+ "value": "30px",
+ "description": "App redesign proposal v0.1; shared landing aliases where applicable. No code implementation yet."
+ },
+ {
+ "name": "--spacing-app-icon",
+ "type": "spacing",
+ "value": "16px",
+ "description": "App redesign proposal v0.1; shared landing aliases where applicable. No code implementation yet."
+ },
+ {
+ "name": "--spacing-app-activity-row",
+ "type": "spacing",
+ "value": "44px",
+ "description": "Compact stacked activity, email, meeting and inbox rows. Shared 4px vertical and 8px horizontal padding."
+ },
+ {
+ "name": "--spacing-app-dashboard-row",
+ "type": "spacing",
+ "value": "56px",
+ "description": "Roomier dashboard rows. Inbox remains the compact 44px rhythm."
+ },
+ {
+ "name": "--spacing-overflow-fade",
+ "type": "spacing",
+ "value": "16px",
+ "description": "One linear ramp everywhere: 100% until 16px before the visible clipping edge, then 0% at that edge. No fade on an unclipped resting filename. Hover controls overlay; they never reserve space. At an adjacent action, the clipping edge is 8px before its hit target."
+ },
+ {
+ "name": "--spacing-overflow-gap",
+ "type": "spacing",
+ "value": "8px",
+ "description": "Distance from the end of a content fade to the adjacent action hit target, on sidebar rows, file cards, and all shared overflow surfaces."
+ },
+ {
+ "name": "--spacing-chart-plot",
+ "type": "spacing",
+ "value": "128px",
+ "description": "Compact Home module plot. Data, empty, loading and unavailable states reserve the same geometry."
+ },
+ {
+ "name": "--spacing-chat-turn",
+ "type": "spacing",
+ "value": "24px",
+ "description": "Shared 24px spacing between conversation turns. Aligns with the platform spacing scale."
+ },
+ {
+ "name": "--spacing-app-gutter-mobile",
+ "type": "spacing",
+ "value": "16px",
+ "description": "Page gutters below 768px; shared module row inset remains 8px."
+ },
+ {
+ "name": "--spacing-app-gutter-compact",
+ "type": "spacing",
+ "value": "24px",
+ "description": "Page gutters from 768px to 1279px; default collapsed sidebar."
+ },
+ {
+ "name": "--spacing-app-gutter-desktop",
+ "type": "spacing",
+ "value": "48px",
+ "description": "Minimum content gutters at 1280px and above; center capped content in remaining space."
+ },
+ {
+ "name": "--spacing-app-touch-target",
+ "type": "spacing",
+ "value": "44px",
+ "description": "Touch hit area. Icon glyphs remain 16px and the same stroke weight."
+ },
+ {
+ "name": "--spacing-auth-control",
+ "type": "spacing",
+ "value": "var(--spacing-app-control-md)",
+ "description": "Auth uses a 32px visible control height. On touch, use transparent 44px hit areas around the compact visible controls."
+ },
+ {
+ "name": "--spacing-docs-gutter",
+ "type": "spacing",
+ "value": "48px"
+ },
+ {
+ "name": "--radius-full",
+ "type": "radius",
+ "value": "9999px",
+ "description": "Figma: radius/full; mode Default."
+ },
+ {
+ "name": "--radius-menu",
+ "type": "radius",
+ "value": "12px",
+ "description": "Figma: radius/menu; mode Default."
+ },
+ {
+ "name": "--radius-item",
+ "type": "radius",
+ "value": "6px",
+ "description": "Figma: radius/item; mode Default."
+ },
+ {
+ "name": "--radius-app-sm",
+ "type": "radius",
+ "value": "6px",
+ "description": "App redesign proposal v0.1; shared landing aliases where applicable. No code implementation yet."
+ },
+ {
+ "name": "--radius-app-md",
+ "type": "radius",
+ "value": "8px",
+ "description": "App redesign proposal v0.1; shared landing aliases where applicable. No code implementation yet."
+ },
+ {
+ "name": "--radius-app-menu",
+ "type": "radius",
+ "value": "10px",
+ "description": "App redesign proposal v0.1; shared landing aliases where applicable. No code implementation yet."
+ },
+ {
+ "name": "--radius-app-dialog",
+ "type": "radius",
+ "value": "16px",
+ "description": "App redesign proposal v0.1; shared landing aliases where applicable. No code implementation yet."
+ },
+ {
+ "name": "--radius-app-composer",
+ "type": "radius",
+ "value": "20px",
+ "description": "App redesign proposal v0.1; shared landing aliases where applicable. No code implementation yet."
+ },
+ {
+ "name": "--radius-panel",
+ "type": "radius",
+ "value": "var(--radius-app-dialog)",
+ "description": "Landing inspection tickets inherit the platform's 16px dialog corner."
+ }
+ ]
+}
diff --git a/packages/auth-ui/package.json b/packages/auth-ui/package.json
new file mode 100644
index 00000000000..4b76e4f2080
--- /dev/null
+++ b/packages/auth-ui/package.json
@@ -0,0 +1,36 @@
+{
+ "name": "@sim/auth-ui",
+ "version": "0.1.0",
+ "private": true,
+ "type": "module",
+ "sideEffects": [
+ "**/*.css"
+ ],
+ "exports": {
+ ".": "./src/index.ts",
+ "./styles.module.css": "./src/styles.module.css"
+ },
+ "scripts": {
+ "type-check": "tsc --noEmit",
+ "lint": "biome check --write --unsafe .",
+ "lint:check": "biome check .",
+ "format": "biome format --write .",
+ "format:check": "biome format ."
+ },
+ "dependencies": {
+ "@radix-ui/react-popover": "1.3.1",
+ "@radix-ui/react-dropdown-menu": "2.2.1",
+ "clsx": "^2.1.1",
+ "input-otp": "1.5.0"
+ },
+ "peerDependencies": {
+ "react": "^19",
+ "react-dom": "^19"
+ },
+ "devDependencies": {
+ "@sim/tsconfig": "workspace:*",
+ "@types/react": "^19",
+ "@types/react-dom": "^19",
+ "typescript": "^7.0.2"
+ }
+}
diff --git a/packages/auth-ui/src/assets/geist-LICENSE.txt b/packages/auth-ui/src/assets/geist-LICENSE.txt
new file mode 100644
index 00000000000..8d003fec6b8
--- /dev/null
+++ b/packages/auth-ui/src/assets/geist-LICENSE.txt
@@ -0,0 +1,92 @@
+Copyright (c) 2023 Vercel, in collaboration with basement.studio
+
+This Font Software is licensed under the SIL Open Font License, Version 1.1.
+This license is copied below, and is also available with a FAQ at:
+http://scripts.sil.org/OFL
+
+-----------------------------------------------------------
+SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
+-----------------------------------------------------------
+
+PREAMBLE
+The goals of the Open Font License (OFL) are to stimulate worldwide
+development of collaborative font projects, to support the font creation
+efforts of academic and linguistic communities, and to provide a free and
+open framework in which fonts may be shared and improved in partnership
+with others.
+
+The OFL allows the licensed fonts to be used, studied, modified and
+redistributed freely as long as they are not sold by themselves. The
+fonts, including any derivative works, can be bundled, embedded,
+redistributed and/or sold with any software provided that any reserved
+names are not used by derivative works. The fonts and derivatives,
+however, cannot be released under any other type of license. The
+requirement for fonts to remain under this license does not apply
+to any document created using the fonts or their derivatives.
+
+DEFINITIONS
+"Font Software" refers to the set of files released by the Copyright
+Holder(s) under this license and clearly marked as such. This may
+include source files, build scripts and documentation.
+
+"Reserved Font Name" refers to any names specified as such after the
+copyright statement(s).
+
+"Original Version" refers to the collection of Font Software components as
+distributed by the Copyright Holder(s).
+
+"Modified Version" refers to any derivative made by adding to, deleting,
+or substituting -- in part or in whole -- any of the components of the
+Original Version, by changing formats or by porting the Font Software to a
+new environment.
+
+"Author" refers to any designer, engineer, programmer, technical
+writer or other person who contributed to the Font Software.
+
+PERMISSION AND CONDITIONS
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of the Font Software, to use, study, copy, merge, embed, modify,
+redistribute, and sell modified and unmodified copies of the Font
+Software, subject to the following conditions:
+
+1) Neither the Font Software nor any of its individual components,
+in Original or Modified Versions, may be sold by itself.
+
+2) Original or Modified Versions of the Font Software may be bundled,
+redistributed and/or sold with any software, provided that each copy
+contains the above copyright notice and this license. These can be
+included either as stand-alone text files, human-readable headers or
+in the appropriate machine-readable metadata fields within text or
+binary files as long as those fields can be easily viewed by the user.
+
+3) No Modified Version of the Font Software may use the Reserved Font
+Name(s) unless explicit written permission is granted by the corresponding
+Copyright Holder. This restriction only applies to the primary font name as
+presented to the users.
+
+4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
+Software shall not be used to promote, endorse or advertise any
+Modified Version, except to acknowledge the contribution(s) of the
+Copyright Holder(s) and the Author(s) or with their explicit written
+permission.
+
+5) The Font Software, modified or unmodified, in part or in whole,
+must be distributed entirely under this license, and must not be
+distributed under any other license. The requirement for fonts to
+remain under this license does not apply to any document created
+using the Font Software.
+
+TERMINATION
+This license becomes null and void if any of the above conditions are
+not met.
+
+DISCLAIMER
+THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
+OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
+COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
+DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
+OTHER DEALINGS IN THE FONT SOFTWARE.
diff --git a/packages/auth-ui/src/assets/geist-mono-LICENSE.txt b/packages/auth-ui/src/assets/geist-mono-LICENSE.txt
new file mode 100644
index 00000000000..8d003fec6b8
--- /dev/null
+++ b/packages/auth-ui/src/assets/geist-mono-LICENSE.txt
@@ -0,0 +1,92 @@
+Copyright (c) 2023 Vercel, in collaboration with basement.studio
+
+This Font Software is licensed under the SIL Open Font License, Version 1.1.
+This license is copied below, and is also available with a FAQ at:
+http://scripts.sil.org/OFL
+
+-----------------------------------------------------------
+SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
+-----------------------------------------------------------
+
+PREAMBLE
+The goals of the Open Font License (OFL) are to stimulate worldwide
+development of collaborative font projects, to support the font creation
+efforts of academic and linguistic communities, and to provide a free and
+open framework in which fonts may be shared and improved in partnership
+with others.
+
+The OFL allows the licensed fonts to be used, studied, modified and
+redistributed freely as long as they are not sold by themselves. The
+fonts, including any derivative works, can be bundled, embedded,
+redistributed and/or sold with any software provided that any reserved
+names are not used by derivative works. The fonts and derivatives,
+however, cannot be released under any other type of license. The
+requirement for fonts to remain under this license does not apply
+to any document created using the fonts or their derivatives.
+
+DEFINITIONS
+"Font Software" refers to the set of files released by the Copyright
+Holder(s) under this license and clearly marked as such. This may
+include source files, build scripts and documentation.
+
+"Reserved Font Name" refers to any names specified as such after the
+copyright statement(s).
+
+"Original Version" refers to the collection of Font Software components as
+distributed by the Copyright Holder(s).
+
+"Modified Version" refers to any derivative made by adding to, deleting,
+or substituting -- in part or in whole -- any of the components of the
+Original Version, by changing formats or by porting the Font Software to a
+new environment.
+
+"Author" refers to any designer, engineer, programmer, technical
+writer or other person who contributed to the Font Software.
+
+PERMISSION AND CONDITIONS
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of the Font Software, to use, study, copy, merge, embed, modify,
+redistribute, and sell modified and unmodified copies of the Font
+Software, subject to the following conditions:
+
+1) Neither the Font Software nor any of its individual components,
+in Original or Modified Versions, may be sold by itself.
+
+2) Original or Modified Versions of the Font Software may be bundled,
+redistributed and/or sold with any software, provided that each copy
+contains the above copyright notice and this license. These can be
+included either as stand-alone text files, human-readable headers or
+in the appropriate machine-readable metadata fields within text or
+binary files as long as those fields can be easily viewed by the user.
+
+3) No Modified Version of the Font Software may use the Reserved Font
+Name(s) unless explicit written permission is granted by the corresponding
+Copyright Holder. This restriction only applies to the primary font name as
+presented to the users.
+
+4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
+Software shall not be used to promote, endorse or advertise any
+Modified Version, except to acknowledge the contribution(s) of the
+Copyright Holder(s) and the Author(s) or with their explicit written
+permission.
+
+5) The Font Software, modified or unmodified, in part or in whole,
+must be distributed entirely under this license, and must not be
+distributed under any other license. The requirement for fonts to
+remain under this license does not apply to any document created
+using the Font Software.
+
+TERMINATION
+This license becomes null and void if any of the above conditions are
+not met.
+
+DISCLAIMER
+THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
+OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
+COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
+DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
+OTHER DEALINGS IN THE FONT SOFTWARE.
diff --git a/packages/auth-ui/src/assets/geist-mono-variable.woff2 b/packages/auth-ui/src/assets/geist-mono-variable.woff2
new file mode 100644
index 00000000000..0e746fc1822
Binary files /dev/null and b/packages/auth-ui/src/assets/geist-mono-variable.woff2 differ
diff --git a/packages/auth-ui/src/assets/geist-variable.woff2 b/packages/auth-ui/src/assets/geist-variable.woff2
new file mode 100644
index 00000000000..5c5999d617e
Binary files /dev/null and b/packages/auth-ui/src/assets/geist-variable.woff2 differ
diff --git a/packages/auth-ui/src/code-entry.tsx b/packages/auth-ui/src/code-entry.tsx
new file mode 100644
index 00000000000..892d2f5dc09
--- /dev/null
+++ b/packages/auth-ui/src/code-entry.tsx
@@ -0,0 +1,91 @@
+'use client'
+
+import { useId } from 'react'
+import { AuthFormMessage } from './compositions'
+import { AuthOTP, AuthOTPGroup, AuthOTPSlot } from './controls'
+import { AuthButton, AuthTextLink } from './primitives'
+import styles from './styles.module.css'
+
+interface AuthCodeEntryProps {
+ value: string
+ onChange: (value: string) => void
+ onVerify: () => void
+ onResend?: () => void
+ onChangeEmail?: () => void
+ verifying: boolean
+ resending: boolean
+ countdown: number
+ error?: string | null
+ help?: string
+}
+
+/** Six-digit entry shared by account verification and independent resource gates. */
+export function AuthCodeEntry({
+ value,
+ onChange,
+ onVerify,
+ onResend,
+ onChangeEmail,
+ verifying,
+ resending,
+ countdown,
+ error,
+ help,
+}: AuthCodeEntryProps) {
+ const errorId = useId()
+ const busy = verifying || resending
+ return (
+
+
+
+ {[0, 1, 2, 3, 4, 5].map((index) => (
+
+ ))}
+
+
+ {error && (
+
+ )}
+
+ Verify email
+
+ {onResend && (
+
+ Didn’t receive a code?{' '}
+ {countdown > 0 ? (
+ Resend in {countdown}s
+ ) : (
+
+ {resending ? 'Sending…' : 'Resend'}
+
+ )}
+
+ )}
+ {help &&
{help}
}
+ {onChangeEmail && (
+
+ )}
+
+ )
+}
diff --git a/packages/auth-ui/src/compositions.tsx b/packages/auth-ui/src/compositions.tsx
new file mode 100644
index 00000000000..962fc852b68
--- /dev/null
+++ b/packages/auth-ui/src/compositions.tsx
@@ -0,0 +1,286 @@
+import type { ReactNode } from 'react'
+import { clsx } from 'clsx'
+import { AuthIcon } from './icons'
+import styles from './styles.module.css'
+
+interface AuthHeaderProps {
+ title: string
+ titleId?: string
+ description?: ReactNode
+}
+/** Owns the page heading; description accepts inline content rendered in a paragraph. */
+export function AuthHeader({ title, titleId, description }: AuthHeaderProps) {
+ return (
+
+
{title}
+ {description != null &&
{description}
}
+
+ )
+}
+
+interface AuthFieldProps {
+ htmlFor: string
+ label: string
+ errors?: string[]
+ hint?: ReactNode
+ action?: ReactNode
+ children: ReactNode
+}
+/** Connects a label and feedback to a child whose id equals htmlFor.
+ * AuthInput with error=true references the generated `${htmlFor}-errors` region.
+ * Callers supplying a hint include `${htmlFor}-hint` in the input's aria-describedby. */
+export function AuthField({ htmlFor, label, errors, hint, action, children }: AuthFieldProps) {
+ return (
+
+
+
+ {label}
+
+ {action}
+
+ {children}
+ {hint && (
+
+ {hint}
+
+ )}
+ {Boolean(errors?.length) && (
+
+ {errors?.map((error) => (
+
{error}
+ ))}
+
+ )}
+
+ )
+}
+
+interface AuthFormMessageProps {
+ type: 'error' | 'success' | 'info'
+ align?: 'left' | 'center'
+ children: ReactNode
+}
+/** Announces errors as alerts and other feedback as status, without owning validation. */
+export function AuthFormMessage({ type, align = 'left', children }: AuthFormMessageProps) {
+ return (
+
+ )
+}
+
+interface AuthDividerProps {
+ label?: string
+}
+/** Separates alternate actions; an omitted label leaves only the divider line. */
+export function AuthDivider({ label }: AuthDividerProps) {
+ return {label && {label} }
+}
+
+interface AuthNavPromptProps {
+ prompt?: string
+ href: string
+ linkLabel: string
+ onNavigate?: () => void
+}
+/** Links between Auth routes with full-document navigation; the host preserves return context. */
+export function AuthNavPrompt({ prompt, href, linkLabel, onNavigate }: AuthNavPromptProps) {
+ return (
+
+ )
+}
+
+interface AuthShellViewProps {
+ children: ReactNode
+ logo: ReactNode
+ homeHref: string
+ navigation?: ReactNode
+ footer?: ReactNode
+ titlebar?: ReactNode
+}
+/** Lays out host-supplied branding, navigation, native titlebar, and footer inside AuthRoot.
+ * The shared column owns responsive sizing, while the host owns authentication and routing. */
+export function AuthShellView({
+ children,
+ logo,
+ homeHref,
+ navigation,
+ footer,
+ titlebar,
+}: AuthShellViewProps) {
+ return (
+
+ {titlebar}
+
+
+ {children}
+
+ {footer &&
}
+
+ )
+}
+
+interface AuthStatusContentProps {
+ title: string
+ titleId?: string
+ description?: ReactNode
+ detail?: ReactNode
+ children?: ReactNode
+}
+/** Composes a status heading, optional detail, and recovery actions in the Auth column. */
+export function AuthStatusContent({
+ title,
+ titleId,
+ description,
+ detail,
+ children,
+}: AuthStatusContentProps) {
+ return (
+
+
+ {detail}
+ {children &&
{children}
}
+
+ )
+}
+
+interface AuthIdentityProps {
+ email: string
+ name?: string
+ label?: string
+ action?: ReactNode
+}
+/** Displays an account identity; the host supplies any account-switch action. */
+export function AuthIdentity({ email, name, label = 'Signed in as', action }: AuthIdentityProps) {
+ return (
+
+
+ {(name || email).slice(0, 1).toUpperCase()}
+
+
+
{name || label}
+
{email}
+
+ {action}
+
+ )
+}
+
+interface AuthResourceRowProps {
+ icon?: ReactNode
+ title: ReactNode
+ description?: ReactNode
+ actions?: ReactNode
+ children?: ReactNode
+ className?: string
+ disabled?: boolean
+}
+/** Wraps resource details and responsive actions; disabled does not disable child controls. */
+export function AuthResourceRow({
+ icon,
+ title,
+ description,
+ actions,
+ children,
+ className,
+ disabled,
+}: AuthResourceRowProps) {
+ return (
+
+
{icon}
+
+
{title}
+ {description &&
{description}
}
+ {children}
+
+ {actions &&
{actions}
}
+
+ )
+}
+
+interface AuthSectionProps {
+ title: string
+ children: ReactNode
+ className?: string
+ description?: ReactNode
+}
+/** Groups related controls under a section heading without reserving absent description space. */
+export function AuthSection({ title, children, className, description }: AuthSectionProps) {
+ return (
+
+
+
{title}
+ {description &&
{description}
}
+
+ {children}
+
+ )
+}
+
+interface AuthLegalProps {
+ action: string
+ termsHref: string
+ privacyHref: string
+}
+/** Uses host-supplied legal URLs, opening them separately without losing form state. */
+export function AuthLegal({ action, termsHref, privacyHref }: AuthLegalProps) {
+ return (
+
+ By {action}, you agree to our{' '}
+
+ Terms of Service
+ {' '}
+ and{' '}
+
+ Privacy Policy
+
+ .
+
+ )
+}
+
+interface AuthStackProps {
+ children: ReactNode
+ className?: string
+}
+/** Applies the shared responsive section rhythm; className may adjust layout. */
+export function AuthStack({ children, className }: AuthStackProps) {
+ return {children}
+}
+/** Pairs provider actions in two columns, with an odd final action spanning both. */
+export function AuthProviderGroup({ children, className }: AuthStackProps) {
+ return {children}
+}
+
+interface AuthResourceIdentityProps {
+ initial: string
+ logoUrl?: string | null
+}
+/** Displays a decorative resource logo or initial beside its accessible name. */
+export function AuthResourceIdentity({ initial, logoUrl }: AuthResourceIdentityProps) {
+ return (
+
+ {logoUrl ? : initial}
+
+ )
+}
diff --git a/packages/auth-ui/src/controls.tsx b/packages/auth-ui/src/controls.tsx
new file mode 100644
index 00000000000..c5717e996ea
--- /dev/null
+++ b/packages/auth-ui/src/controls.tsx
@@ -0,0 +1,512 @@
+'use client'
+
+import {
+ type ComponentType,
+ createContext,
+ forwardRef,
+ type HTMLAttributes,
+ type InputHTMLAttributes,
+ type ReactNode,
+ useContext,
+ useEffect,
+ useId,
+ useRef,
+ useState,
+} from 'react'
+import * as DropdownMenu from '@radix-ui/react-dropdown-menu'
+import * as Popover from '@radix-ui/react-popover'
+import { clsx } from 'clsx'
+import { OTPInput, OTPInputContext } from 'input-otp'
+import { createPortal } from 'react-dom'
+import { AuthFormMessage } from './compositions'
+import { AuthIcon } from './icons'
+import styles from './styles.module.css'
+import './utilities.css'
+
+const PortalContext = createContext(null)
+
+interface AuthRootProps {
+ children: ReactNode
+ className?: string
+}
+/** Provides the Auth styling and portal boundary without owning authentication state. */
+export function AuthRoot({ children, className }: AuthRootProps) {
+ const [portal, setPortal] = useState(null)
+ return (
+
+ )
+}
+
+interface AuthNotificationProps {
+ children: ReactNode
+ type: 'success' | 'error'
+}
+/** Feedback stays inside the Auth portal and does not inherit the app toast theme. */
+export function AuthNotification({ children, type }: AuthNotificationProps) {
+ const portal = useContext(PortalContext)
+ const [visible, setVisible] = useState(true)
+ const [hovered, setHovered] = useState(false)
+ useEffect(() => {
+ if (hovered) return
+ const timer = setTimeout(() => setVisible(false), 5000)
+ return () => clearTimeout(timer)
+ }, [hovered])
+ if (!portal || !visible) return null
+ return createPortal(
+ setHovered(true)}
+ onPointerLeave={() => setHovered(false)}
+ >
+
{children}
+
,
+ portal
+ )
+}
+
+interface AuthInputProps extends Omit, 'size'> {
+ error?: boolean
+ icon?: ComponentType<{ className?: string }>
+ startAdornment?: ReactNode
+ endAdornment?: ReactNode
+ inputClassName?: string
+}
+/** Forwards native input props and ref inside the Auth control and mobile touch lane.
+ * With error and id supplied, associates the `${id}-errors` region from AuthField. */
+export const AuthInput = forwardRef(function AuthInput(
+ {
+ className,
+ inputClassName,
+ error,
+ icon: Icon,
+ startAdornment,
+ endAdornment,
+ disabled,
+ id,
+ ...props
+ },
+ ref
+) {
+ const describedBy =
+ [props['aria-describedby'], error && id ? `${id}-errors` : undefined]
+ .filter(Boolean)
+ .join(' ') || undefined
+ return (
+ {
+ if (event.target === event.currentTarget) {
+ event.preventDefault()
+ event.currentTarget.querySelector('input')?.focus()
+ }
+ }}
+ >
+
+ {startAdornment ?? (Icon && )}
+
+ {endAdornment}
+
+
+ )
+})
+
+/** Adds overridable email entry defaults while leaving input type and validation to the caller. */
+export const AuthEmailInput = forwardRef(
+ function AuthEmailInput(props, ref) {
+ return (
+
+ )
+ }
+)
+
+interface PasswordInputProps extends Omit {}
+/** Keeps password visibility local; forwards field identity, validation, and autocomplete props. */
+export function PasswordInput({
+ disabled,
+ placeholder = 'Enter your password',
+ ...props
+}: PasswordInputProps) {
+ const [visible, setVisible] = useState(false)
+ return (
+ setVisible(!visible)}
+ >
+
+
+ }
+ />
+ )
+}
+
+interface AuthReadonlyInputProps {
+ value: string
+ label?: string
+}
+/** Provides a labeled pairing code with copy feedback and manual selection on clipboard failure. */
+export function AuthReadonlyInput({ value, label = 'Pairing code' }: AuthReadonlyInputProps) {
+ const ref = useRef(null)
+ const [status, setStatus] = useState<'idle' | 'copied' | 'failed'>('idle')
+ const statusId = useId()
+ const inputId = useId()
+ useEffect(() => {
+ if (status !== 'copied') return
+ const timer = setTimeout(() => setStatus('idle'), 2000)
+ return () => clearTimeout(timer)
+ }, [status])
+ async function copy() {
+ try {
+ await navigator.clipboard.writeText(value)
+ setStatus('copied')
+ } catch {
+ setStatus('failed')
+ ref.current?.focus()
+ ref.current?.select()
+ }
+ }
+ return (
+
+
+ {label}
+
+
+
+
+ }
+ />
+
+ {status === 'copied'
+ ? 'Copied'
+ : status === 'failed'
+ ? 'Could not copy. Select the code and copy it manually.'
+ : null}
+
+
+ )
+}
+
+interface AuthOTPProps
+ extends Omit<
+ InputHTMLAttributes,
+ 'value' | 'onChange' | 'maxLength' | 'children'
+ > {
+ value?: string
+ onChange?: (value: string) => void
+ maxLength: number
+ children: ReactNode
+ containerClassName?: string
+ onComplete?: (value: string) => void
+}
+/** Adapts a controlled numeric code input; the host supplies its accessible name and feedback IDs. */
+export function AuthOTP({ className, containerClassName, ...props }: AuthOTPProps) {
+ return (
+
+ )
+}
+interface AuthOTPGroupProps extends HTMLAttributes {}
+/** Groups visual code slots inside AuthOTP while the underlying input owns keyboard interaction. */
+export function AuthOTPGroup({ className, ...props }: AuthOTPGroupProps) {
+ return
+}
+interface AuthOTPSlotProps extends HTMLAttributes {
+ index: number
+}
+/** Displays one indexed slot from the surrounding AuthOTP context. */
+export function AuthOTPSlot({ index, className, ...props }: AuthOTPSlotProps) {
+ const { slots } = useContext(OTPInputContext)
+ const slot = slots[index]
+ return (
+
+ {slot?.char}
+ {slot?.hasFakeCaret && }
+
+ )
+}
+
+export interface AuthSelectOption {
+ label: string
+ value: string
+ disabled?: boolean
+}
+interface AuthSelectProps {
+ options: AuthSelectOption[]
+ value?: string
+ onChange?: (value: string) => void
+ disabled?: boolean
+ placeholder?: string
+ displayLabel?: string
+ searchable?: boolean
+ searchPlaceholder?: string
+ fullWidth?: boolean
+ dropdownWidth?: string
+ className?: string
+ id?: string
+ 'aria-label'?: string
+}
+/** Shared accessible option list for terminal workspaces and provider installation choices. */
+export function AuthSelect({
+ options,
+ value,
+ onChange,
+ disabled,
+ placeholder = 'Select an option',
+ displayLabel,
+ searchable,
+ searchPlaceholder = 'Search',
+ className,
+ id,
+ 'aria-label': ariaLabel,
+}: AuthSelectProps) {
+ const portal = useContext(PortalContext)
+ const [open, setOpen] = useState(false)
+ const [search, setSearch] = useState('')
+ const [active, setActive] = useState(0)
+ const listId = useId()
+ const trigger = useRef(null)
+ const content = useRef(null)
+ const filtered = options.filter((option) =>
+ option.label.toLowerCase().includes(search.toLowerCase())
+ )
+ const selected = options.find((option) => option.value === value)
+ const available = filtered.filter((option) => !option.disabled)
+ const activeValue = available[active]?.value
+ function choose(next: string) {
+ onChange?.(next)
+ setOpen(false)
+ setSearch('')
+ }
+ function move(next: number) {
+ const index = Math.max(0, Math.min(available.length - 1, next))
+ setActive(index)
+ content.current
+ ?.querySelectorAll('[role="option"]:not([aria-disabled="true"])')
+ [index]?.scrollIntoView({ block: 'nearest' })
+ }
+ return (
+ {
+ setOpen(next)
+ setSearch('')
+ setActive(
+ Math.max(
+ 0,
+ options
+ .filter((option) => !option.disabled)
+ .findIndex((option) => option.value === value)
+ )
+ )
+ }}
+ >
+
+ {
+ if (event.key === 'ArrowDown' || event.key === 'ArrowUp') {
+ event.preventDefault()
+ setSearch('')
+ setActive(
+ Math.max(
+ 0,
+ options
+ .filter((option) => !option.disabled)
+ .findIndex((option) => option.value === value)
+ )
+ )
+ setOpen(true)
+ }
+ }}
+ >
+
+ {displayLabel ?? selected?.label ?? placeholder}
+
+
+
+
+
+ {
+ if (event.key === 'ArrowDown' || event.key === 'ArrowUp') {
+ event.preventDefault()
+ move(active + (event.key === 'ArrowDown' ? 1 : -1))
+ } else if (
+ (event.key === 'Home' || event.key === 'End') &&
+ !(event.target instanceof HTMLInputElement)
+ ) {
+ event.preventDefault()
+ move(event.key === 'Home' ? 0 : available.length - 1)
+ } else if (event.key === 'Enter' && activeValue) {
+ event.preventDefault()
+ choose(activeValue)
+ }
+ }}
+ onOpenAutoFocus={(event) => {
+ if (!searchable) {
+ event.preventDefault()
+ content.current?.querySelector('[role="listbox"]')?.focus()
+ }
+ }}
+ tabIndex={-1}
+ >
+ {searchable && (
+ {
+ setSearch(event.target.value)
+ setActive(0)
+ }}
+ />
+ )}
+
+ {filtered.map((option) => (
+
{
+ if (!option.disabled) setActive(available.indexOf(option))
+ }}
+ onClick={() => {
+ if (!option.disabled) choose(option.value)
+ }}
+ >
+
{option.label}
+
+ {option.value === value && }
+
+
+ ))}
+ {filtered.length === 0 &&
No matching options
}
+
+
+
+
+ )
+}
+
+interface AuthAccountMenuProps {
+ email: string
+ disabled?: boolean
+ pending?: boolean
+ onSwitch: () => void
+}
+/** Keeps account actions in the Auth portal; the host performs sign-out through onSwitch. */
+export function AuthAccountMenu({ email, disabled, pending, onSwitch }: AuthAccountMenuProps) {
+ const portal = useContext(PortalContext)
+ return (
+
+
+
+
+ {email.slice(0, 1).toUpperCase()}
+
+
+ {pending ? 'Signing out…' : 'Signed in as'}
+ {email}
+
+
+
+
+
+
+ {email}
+
+ {pending ? 'Signing out…' : 'Use another account'}
+
+
+
+
+ )
+}
diff --git a/packages/auth-ui/src/icons.tsx b/packages/auth-ui/src/icons.tsx
new file mode 100644
index 00000000000..6134e2a3da0
--- /dev/null
+++ b/packages/auth-ui/src/icons.tsx
@@ -0,0 +1,74 @@
+import type { SVGProps } from 'react'
+
+interface AuthIconProps extends SVGProps {
+ name: 'eye' | 'eye-off' | 'copy' | 'check' | 'chevron' | 'lock' | 'error'
+}
+
+/** Auth glyphs with locally owned outlines, dimensions, and treatment. */
+export function AuthIcon({ name, ...props }: AuthIconProps) {
+ const views = {
+ eye: '0 0 24 24',
+ 'eye-off': '0 0 24 24',
+ copy: '-1 -2 24 24',
+ check: '-2 -2.75 24 24',
+ chevron: '-1.75 -2.25 24 24',
+ lock: '-1.75 -2 24 24',
+ error: '0 0 24 24',
+ } as const
+ return (
+
+ {name === 'eye' && (
+ <>
+
+
+ >
+ )}
+ {name === 'eye-off' && (
+ <>
+
+
+ >
+ )}
+ {name === 'copy' && (
+ <>
+
+
+ >
+ )}
+ {name === 'check' && (
+ <>
+
+ >
+ )}
+ {name === 'chevron' && (
+ <>
+
+ >
+ )}
+ {name === 'lock' && (
+ <>
+
+
+ >
+ )}
+ {name === 'error' && (
+ <>
+
+
+
+ >
+ )}
+
+ )
+}
diff --git a/packages/auth-ui/src/index.ts b/packages/auth-ui/src/index.ts
new file mode 100644
index 00000000000..38eb312af60
--- /dev/null
+++ b/packages/auth-ui/src/index.ts
@@ -0,0 +1,42 @@
+export { clsx as cn } from 'clsx'
+export { AuthCodeEntry } from './code-entry'
+export {
+ AuthDivider,
+ AuthField,
+ AuthFormMessage,
+ AuthHeader,
+ AuthIdentity,
+ AuthLegal,
+ AuthNavPrompt,
+ AuthProviderGroup,
+ AuthResourceIdentity,
+ AuthResourceRow,
+ AuthSection,
+ AuthShellView,
+ AuthStack,
+ AuthStatusContent,
+} from './compositions'
+export type { AuthSelectOption } from './controls'
+export {
+ AuthAccountMenu,
+ AuthEmailInput,
+ AuthInput,
+ AuthNotification,
+ AuthReadonlyInput,
+ AuthRoot,
+ AuthSelect,
+ PasswordInput,
+} from './controls'
+export { AuthIcon } from './icons'
+export type { AuthButtonProps, AuthLinkButtonProps } from './primitives'
+export {
+ AuthButton,
+ AuthLabel,
+ AuthLinkButton,
+ AuthLoader,
+ AuthSkeleton,
+ AuthTag,
+ AuthTextLink,
+} from './primitives'
+export { AuthGithubIcon, AuthGoogleIcon, AuthMicrosoftIcon } from './provider-icons'
+export { AuthWordmark } from './wordmark'
diff --git a/packages/auth-ui/src/primitives.tsx b/packages/auth-ui/src/primitives.tsx
new file mode 100644
index 00000000000..063264e05fb
--- /dev/null
+++ b/packages/auth-ui/src/primitives.tsx
@@ -0,0 +1,170 @@
+import type {
+ AnchorHTMLAttributes,
+ ButtonHTMLAttributes,
+ ComponentType,
+ HTMLAttributes,
+ LabelHTMLAttributes,
+ ReactNode,
+} from 'react'
+import { clsx } from 'clsx'
+import styles from './styles.module.css'
+
+export interface AuthButtonProps extends ButtonHTMLAttributes {
+ variant?:
+ | 'primary'
+ | 'secondary'
+ | 'outline'
+ | 'border'
+ | 'ghost'
+ | 'default'
+ | 'destructive'
+ | 'navigation'
+ fullWidth?: boolean
+ leftIcon?: ComponentType<{ className?: string }>
+ rightIcon?: ComponentType<{ className?: string }>
+ loading?: boolean
+ loadingLabel?: string
+}
+
+/** A compact 32px action inside a separate mobile touch target. */
+export function AuthButton({
+ children,
+ className,
+ variant = 'secondary',
+ fullWidth,
+ leftIcon: Icon,
+ rightIcon: EndIcon,
+ loading,
+ loadingLabel,
+ disabled,
+ type = 'button',
+ ...props
+}: AuthButtonProps) {
+ return (
+
+
+ {loading ? : Icon ? : null}
+ {loading ? (loadingLabel ?? children) : children}
+ {!loading && EndIcon && }
+
+
+ )
+}
+
+export interface AuthLinkButtonProps extends AnchorHTMLAttributes {
+ variant?: AuthButtonProps['variant']
+ fullWidth?: boolean
+}
+
+/** Applies Auth action styling to a native link without introducing client routing. */
+export function AuthLinkButton({
+ className,
+ children,
+ variant = 'secondary',
+ fullWidth,
+ ...props
+}: AuthLinkButtonProps) {
+ return (
+
+ {children}
+
+ )
+}
+
+interface AuthLabelProps extends LabelHTMLAttributes {}
+/** Labels the control identified by htmlFor; feedback association belongs to AuthField. */
+export function AuthLabel({ className, htmlFor, children, ...props }: AuthLabelProps) {
+ return (
+
+ {children}
+
+ )
+}
+
+interface AuthLoaderProps extends HTMLAttributes {
+ animate?: boolean
+}
+/** Decorative spinner with utility-overridable sizing; the loading region owns announcements. */
+export function AuthLoader({ className, animate = true, ...props }: AuthLoaderProps) {
+ return (
+
+ )
+}
+
+interface AuthSkeletonProps extends HTMLAttributes {
+ shape?: 'title' | 'label' | 'control' | 'divider'
+}
+/** Decorative placeholder whose sizing utilities override the shared shape defaults.
+ * Controls reserve the mobile touch lane while retaining the compact visible surface. */
+export function AuthSkeleton({ className, shape = 'control', ...props }: AuthSkeletonProps) {
+ return (
+
+ )
+}
+
+interface AuthTextLinkProps {
+ children: ReactNode
+ href?: string
+ onClick?: () => void
+ external?: boolean
+ disabled?: boolean
+ className?: string
+}
+/** Renders a document link when href is provided, otherwise a button; disabled blocks either action. */
+export function AuthTextLink({
+ children,
+ href,
+ onClick,
+ external,
+ disabled,
+ className,
+}: AuthTextLinkProps) {
+ const classes = clsx(styles.textLink, className)
+ return href ? (
+
+ {children}
+
+ ) : (
+
+ {children}
+
+ )
+}
+
+interface AuthTagProps {
+ children: ReactNode
+ className?: string
+ variant?: string
+}
+/** Presents compact, non-interactive metadata beside Auth content. */
+export function AuthTag({ children, className }: AuthTagProps) {
+ return {children}
+}
diff --git a/packages/auth-ui/src/provider-icons.tsx b/packages/auth-ui/src/provider-icons.tsx
new file mode 100644
index 00000000000..bebdee8e429
--- /dev/null
+++ b/packages/auth-ui/src/provider-icons.tsx
@@ -0,0 +1,58 @@
+import type { SVGProps } from 'react'
+
+interface ProviderIconProps extends SVGProps {}
+
+/** Decorative Google mark; the enclosing provider action supplies its accessible name. */
+export function AuthGoogleIcon(props: ProviderIconProps) {
+ return (
+
+
+
+
+
+
+ )
+}
+
+/** Decorative Microsoft mark; the enclosing provider action supplies its accessible name. */
+export function AuthMicrosoftIcon(props: ProviderIconProps) {
+ return (
+
+
+
+
+
+
+ )
+}
+
+/** Decorative GitHub mark; the enclosing provider action supplies its accessible name. */
+export function AuthGithubIcon(props: ProviderIconProps) {
+ return (
+
+
+
+ )
+}
diff --git a/packages/auth-ui/src/styles.d.ts b/packages/auth-ui/src/styles.d.ts
new file mode 100644
index 00000000000..bc5e482353c
--- /dev/null
+++ b/packages/auth-ui/src/styles.d.ts
@@ -0,0 +1,6 @@
+declare module '*.module.css' {
+ const classes: Record
+ export default classes
+}
+
+declare module '*.css'
diff --git a/packages/auth-ui/src/styles.module.css b/packages/auth-ui/src/styles.module.css
new file mode 100644
index 00000000000..a908ffbf94f
--- /dev/null
+++ b/packages/auth-ui/src/styles.module.css
@@ -0,0 +1,824 @@
+@reference 'tailwindcss';
+
+@font-face {
+ font-family: "Sim Auth Geist";
+ src: url("./assets/geist-variable.woff2") format("woff2");
+ font-style: normal;
+ font-weight: 100 900;
+ font-display: swap;
+}
+@font-face {
+ font-family: "Sim Auth Geist Mono";
+ src: url("./assets/geist-mono-variable.woff2") format("woff2");
+ font-style: normal;
+ font-weight: 100 900;
+ font-display: swap;
+}
+
+.root {
+ --auth-canvas: #fff;
+ --auth-primary: rgb(10 10 10 / 80%);
+ --auth-secondary: rgb(10 10 10 / 60%);
+ --auth-reading: rgb(255 255 255 / 96%);
+ --auth-overlay: rgb(255 255 255 / 88%);
+ --auth-border: rgb(0 0 0 / 12%);
+ --auth-border-subtle: rgb(0 0 0 / 6%);
+ --auth-hover: rgb(10 10 10 / 6%);
+ --auth-selected: rgb(0 0 0 / 8%);
+ --auth-pressed: rgb(0 0 0 / 12%);
+ --auth-accent: #06c;
+ --auth-error: #c83232;
+ --auth-disabled: 0.4;
+ --auth-font: "Sim Auth Geist", Arial, sans-serif;
+ --auth-mono: "Sim Auth Geist Mono", monospace;
+ --auth-title-size: 24px;
+ --auth-title-leading: 32px;
+ --auth-body-size: 14px;
+ --auth-body-leading: 22px;
+ --auth-control-size: 13px;
+ --auth-control-leading: 18px;
+ --auth-caption-size: 12px;
+ --auth-input-size: 14px;
+ --auth-control-height: 32px;
+ --auth-control-radius: 8px;
+ --auth-quiet-size: 28px;
+ --auth-icon-size: 16px;
+ --auth-field-gap: 20px;
+ --auth-group-gap: 20px;
+ --auth-label-gap: 8px;
+ --auth-control-inset: 10px;
+ --auth-form-width: 400px;
+ --auth-nav-width: 1280px;
+ --auth-touch-height: 32px;
+ color-scheme: light;
+ color: var(--auth-primary);
+ font-family: var(--auth-font);
+ font-size: var(--auth-body-size);
+ font-weight: 400;
+ font-synthesis: none;
+ line-height: var(--auth-body-leading);
+ letter-spacing: 0;
+ text-align: left;
+ text-transform: none;
+ -webkit-font-smoothing: antialiased;
+}
+.root :where(*, *::before, *::after) {
+ box-sizing: border-box;
+}
+.root :where(h1, h2, h3, p, ul, ol, fieldset) {
+ margin: 0;
+ padding: 0;
+}
+.root :where(ul, ol) {
+ list-style: none;
+}
+.root :where(button, input, select, textarea) {
+ font: inherit;
+ letter-spacing: inherit;
+ text-transform: none;
+ color: inherit;
+}
+.root :where(a) {
+ color: inherit;
+}
+.root :where(button) {
+ cursor: pointer;
+}
+.root :where(button:disabled) {
+ cursor: not-allowed;
+}
+.root :where(svg) {
+ flex-shrink: 0;
+}
+.root :where(button, a, input, [tabindex]):focus-visible {
+ outline: 2px solid var(--auth-accent);
+ outline-offset: 2px;
+}
+.root .page {
+ @apply flex w-full flex-col;
+ min-height: 100svh;
+ background: var(--auth-canvas);
+}
+.root .header {
+ @apply w-full shrink-0;
+ height: 56px;
+}
+.root .nav {
+ @apply mx-auto flex h-full w-full items-center justify-between;
+ max-width: var(--auth-nav-width);
+ padding-inline: 16px;
+}
+.root .wordmark {
+ @apply inline-flex items-center;
+ width: 40px;
+ min-height: 44px;
+}
+.root .wordmark :where(svg, img) {
+ display: block;
+ translate: 0 -2px;
+ color: #000;
+ width: 40px;
+ max-height: 24px;
+ object-fit: contain;
+}
+.root .main {
+ @apply flex w-full flex-1 items-center justify-center;
+ padding: 48px 24px;
+}
+.root .column {
+ @apply w-full shrink-0;
+ max-width: var(--auth-form-width);
+ min-width: 0;
+}
+.root .footer {
+ @apply flex shrink-0 justify-center;
+ padding: 0 16px 24px;
+ font-size: var(--auth-caption-size);
+ line-height: 18px;
+}
+.root .footer .textLink {
+ color: var(--auth-primary);
+}
+.root .heading {
+ @apply flex flex-col;
+ gap: 6px;
+}
+.root .heading h1 {
+ font-family: var(--auth-font);
+ font-size: var(--auth-title-size);
+ line-height: var(--auth-title-leading);
+ font-weight: 500;
+ color: var(--auth-primary);
+ letter-spacing: -0.02em;
+ text-align: left;
+ overflow-wrap: anywhere;
+}
+.root .heading p {
+ color: var(--auth-primary);
+ font-size: var(--auth-body-size);
+ line-height: var(--auth-body-leading);
+ font-weight: 400;
+ overflow-wrap: anywhere;
+}
+.root .stack {
+ @apply flex flex-col;
+ gap: var(--auth-group-gap);
+}
+.root .field {
+ @apply flex w-full flex-col;
+ gap: var(--auth-label-gap);
+}
+.root .labelRow {
+ @apply flex items-center justify-between;
+ gap: 8px;
+}
+.root .labelRow .textLink {
+ font-size: var(--auth-caption-size);
+ line-height: var(--auth-control-leading);
+}
+.root .label {
+ color: var(--auth-primary);
+ font-size: var(--auth-control-size);
+ line-height: var(--auth-control-leading);
+ font-weight: 400;
+}
+.root .caption,
+.root .fieldErrors {
+ font-size: var(--auth-caption-size);
+ line-height: 18px;
+ overflow-wrap: anywhere;
+}
+.root .caption {
+ color: var(--auth-secondary);
+}
+.root .caption:empty {
+ display: none;
+}
+.root .fieldErrors {
+ color: var(--auth-error);
+}
+.root .inputLane,
+.root .select {
+ @apply flex w-full items-center;
+ position: relative;
+ min-width: 0;
+ height: var(--auth-touch-height);
+ padding: 0;
+ background: none;
+ border: 0;
+ box-shadow: none;
+}
+.root .inputSurface {
+ @apply flex w-full items-center;
+ position: relative;
+ min-width: 0;
+ height: var(--auth-control-height);
+ padding-inline: var(--auth-control-inset);
+ border: 1px solid var(--auth-border);
+ border-radius: var(--auth-control-radius);
+ background: var(--auth-reading);
+ gap: 6px;
+}
+.root .inputSurface:has(.quietAction) {
+ padding-right: 2px;
+}
+.root .inputLane:focus-within .inputSurface {
+ border-color: var(--auth-accent);
+ box-shadow: 0 0 0 1px var(--auth-accent);
+}
+.root .inputLane[data-invalid] .inputSurface {
+ border-color: var(--auth-error);
+}
+.root .inputLane[data-disabled] {
+ opacity: var(--auth-disabled);
+}
+.root .input {
+ @apply w-full min-w-0 flex-1;
+ height: 100%;
+ padding: 0;
+ border: 0;
+ border-radius: 0;
+ outline: none;
+ box-shadow: none;
+ background: transparent;
+ font-family: var(--auth-font);
+ font-size: var(--auth-input-size);
+ font-weight: 400;
+ line-height: 18px;
+ color: var(--auth-primary);
+ caret-color: var(--auth-primary);
+ opacity: 1;
+}
+.root .input:focus-visible {
+ outline: none;
+}
+.root .input::placeholder {
+ color: var(--auth-secondary);
+ opacity: 1;
+}
+.root .input:-webkit-autofill {
+ -webkit-text-fill-color: var(--auth-primary);
+ box-shadow: 0 0 0 1000px white inset;
+}
+.root .button {
+ @apply relative inline-flex shrink-0 items-center justify-center;
+ height: var(--auth-touch-height);
+ min-width: 0;
+ padding: 0;
+ border: 0;
+ border-radius: var(--auth-control-radius);
+ background: transparent;
+ color: var(--auth-primary);
+ font-size: var(--auth-control-size);
+ line-height: var(--auth-control-leading);
+ font-weight: 400;
+ text-decoration: none;
+ white-space: nowrap;
+ transition: color 150ms;
+}
+.root .buttonSurface {
+ @apply inline-flex h-full w-full items-center justify-center;
+ height: var(--auth-control-height);
+ min-width: 0;
+ padding-inline: var(--auth-control-inset);
+ gap: 6px;
+ border: 1px solid var(--auth-border-subtle);
+ border-radius: var(--auth-control-radius);
+ background: var(--auth-reading);
+ box-shadow: 0 1px 2px rgb(0 0 0 / 4%);
+ transition: background-color 150ms;
+}
+.root .button[data-variant="primary"] .buttonSurface {
+ background: var(--auth-primary);
+ color: white;
+ border-color: transparent;
+ box-shadow: none;
+}
+.root .button[data-variant="navigation"] .buttonSurface {
+ background: #0a0a0a;
+ color: white;
+ border-color: transparent;
+ border-radius: 9999px;
+ padding-inline: 16px;
+ font-weight: 450;
+ line-height: 19.5px;
+ box-shadow: none;
+}
+.root .button[data-variant="navigation"]:hover .buttonSurface {
+ background: rgb(10 10 10 / 80%);
+}
+.root .button[data-variant="ghost"] .buttonSurface {
+ background: transparent;
+ box-shadow: none;
+ border-color: transparent;
+}
+.root .button:hover:not(:disabled, [data-variant="navigation"]) .buttonSurface {
+ background: var(--auth-hover);
+}
+.root .button[data-variant="primary"]:hover:not(:disabled) .buttonSurface {
+ background: rgb(10 10 10 / 90%);
+}
+.root .button:active:not(:disabled, [data-variant="navigation"]) .buttonSurface {
+ background: var(--auth-pressed);
+}
+.root .button[data-variant="primary"]:active:not(:disabled) .buttonSurface {
+ background: rgb(10 10 10);
+}
+.root .button:disabled {
+ opacity: var(--auth-disabled);
+}
+.root .fullWidth {
+ width: 100%;
+}
+.root .icon,
+.root .buttonSurface svg {
+ width: var(--auth-icon-size);
+ height: var(--auth-icon-size);
+ flex-shrink: 0;
+}
+.root .quietAction {
+ @apply relative inline-flex shrink-0 items-center justify-center;
+ width: var(--auth-quiet-size);
+ height: var(--auth-quiet-size);
+ padding: 0;
+ border: 0;
+ border-radius: 9999px;
+ background: transparent;
+ color: var(--auth-secondary);
+}
+.root .quietAction:hover {
+ background: var(--auth-hover);
+ color: var(--auth-primary);
+}
+.root .textLink {
+ padding: 0;
+ border: 0;
+ background: none;
+ color: var(--auth-secondary);
+ text-decoration: none;
+ font-size: inherit;
+ line-height: inherit;
+ font-weight: 400;
+}
+.root .textLink:not(:disabled):not([aria-disabled="true"]):hover {
+ color: var(--auth-primary);
+ text-decoration: underline;
+ text-underline-offset: 3px;
+}
+.root .textLink:disabled,
+.root .textLink[aria-disabled="true"] {
+ cursor: not-allowed;
+ opacity: var(--auth-disabled);
+}
+.root .feedback {
+ @apply flex items-start;
+ gap: 8px;
+ font-size: var(--auth-control-size);
+ line-height: 18px;
+ color: var(--auth-primary);
+ overflow-wrap: anywhere;
+}
+.root .feedback[data-align="center"] {
+ justify-content: center;
+ text-align: center;
+}
+.root .feedback[data-tone="error"] {
+ color: var(--auth-error);
+}
+.root .feedbackIcon {
+ @apply flex shrink-0 items-center;
+ width: 16px;
+ height: 18px;
+}
+.root .divider {
+ @apply flex w-full items-center;
+ gap: 16px;
+ color: var(--auth-secondary);
+ font-size: var(--auth-caption-size);
+ line-height: 18px;
+}
+.root .divider::before,
+.root .divider::after {
+ content: "";
+ flex: 1;
+ height: 1px;
+ background: var(--auth-border-subtle);
+}
+.root .providers {
+ @apply grid;
+ grid-template-columns: repeat(2, minmax(0, 1fr));
+ gap: 8px;
+}
+.root .providers > :last-child:nth-child(odd) {
+ grid-column: 1 / -1;
+}
+.root .legal {
+ font-size: var(--auth-caption-size);
+ line-height: 18px;
+ text-align: center;
+ color: var(--auth-secondary);
+}
+.root .legal a {
+ color: var(--auth-primary);
+ text-decoration: underline;
+ text-underline-offset: 2px;
+}
+.root .navPrompt {
+ @apply flex flex-wrap items-center;
+ gap: 6px;
+ font-size: var(--auth-control-size);
+ line-height: 18px;
+ color: var(--auth-secondary);
+}
+.root .navPrompt a {
+ text-decoration: underline;
+ text-underline-offset: 3px;
+}
+.root .identity {
+ @apply flex items-start;
+ gap: 8px;
+ padding: 8px;
+ min-height: 40px;
+}
+.root .identityText {
+ @apply min-w-0 flex-1;
+ overflow-wrap: anywhere;
+ font-size: var(--auth-control-size);
+ line-height: 18px;
+}
+.root .identityText p + p {
+ font-size: var(--auth-caption-size);
+ color: var(--auth-secondary);
+}
+.root .avatar {
+ @apply flex shrink-0 items-center justify-center;
+ width: 24px;
+ height: 24px;
+ border-radius: 50%;
+ background: var(--auth-hover);
+ font-size: 12px;
+ line-height: 16px;
+}
+.root .actions {
+ @apply flex flex-wrap items-center;
+ gap: 8px;
+}
+.root .resourceRow {
+ @apply grid;
+ grid-template-columns: 24px minmax(0, 1fr) auto;
+ align-items: start;
+ gap: 8px;
+ padding: 8px;
+}
+.root .resourceIcon {
+ @apply flex items-center justify-center;
+ width: 24px;
+ height: 24px;
+}
+.root .resourceIcon svg {
+ width: 16px;
+ height: 16px;
+}
+.root .resourceText {
+ min-width: 0;
+ font-size: var(--auth-body-size);
+ line-height: 22px;
+ overflow-wrap: anywhere;
+}
+.root .resourceText p {
+ color: var(--auth-secondary);
+ font-size: var(--auth-caption-size);
+ line-height: 18px;
+}
+.root .resourceActions {
+ @apply flex items-center;
+ gap: 8px;
+}
+.root .tag {
+ display: inline-flex;
+ border: 1px solid var(--auth-border-subtle);
+ border-radius: 6px;
+ padding: 2px 6px;
+ color: var(--auth-secondary);
+ font-size: 12px;
+ line-height: 18px;
+}
+.root .sectionHeading h2 {
+ color: var(--auth-primary);
+ font-size: 16px;
+ font-weight: 500;
+ line-height: 24px;
+}
+.root .sectionHeading p {
+ font-size: var(--auth-body-size);
+ line-height: 22px;
+}
+.root .loader {
+ display: inline-block;
+ border: 1.5px solid currentColor;
+ border-right-color: transparent;
+ border-radius: 50%;
+ flex-shrink: 0;
+}
+.root .spinning {
+ animation: spin 800ms linear infinite;
+}
+@layer components {
+ .root .loader {
+ width: 16px;
+ height: 16px;
+ }
+ .root .skeleton {
+ height: var(--auth-touch-height);
+ border-radius: 8px;
+ }
+ .root .skeleton[data-shape="title"] {
+ height: 32px;
+ }
+ .root .skeleton[data-shape="label"] {
+ height: 18px;
+ border-radius: 4px;
+ }
+ .root .skeleton[data-shape="divider"] {
+ height: 1px;
+ border-radius: 0;
+ }
+}
+.root .skeleton {
+ display: flex;
+ align-items: center;
+ max-width: 100%;
+ animation: pulse 1.5s ease-in-out infinite;
+}
+.root .skeleton::before {
+ content: "";
+ display: block;
+ width: 100%;
+ height: 100%;
+ border-radius: inherit;
+ background: var(--auth-hover);
+}
+.root .skeleton[data-shape="control"]::before {
+ height: var(--auth-control-height);
+ max-height: 100%;
+}
+.root .otpLane {
+ @apply relative flex items-center;
+ min-height: var(--auth-touch-height);
+}
+.root .otpLane:has(input:disabled) {
+ opacity: var(--auth-disabled);
+}
+.root .otpGroup {
+ @apply flex items-center;
+ gap: 8px;
+}
+.root .otpSlot {
+ @apply relative flex items-center justify-center;
+ width: 32px;
+ height: 32px;
+ border: 1px solid var(--auth-border);
+ border-radius: 8px;
+ background: var(--auth-reading);
+ font-size: 16px;
+ line-height: 18px;
+}
+.root .otpSlot[data-active] {
+ border-color: var(--auth-accent);
+ outline: 1px solid var(--auth-accent);
+}
+.root .caret {
+ width: 1px;
+ height: 18px;
+ background: var(--auth-primary);
+ animation: pulse 1s infinite;
+}
+.root .select {
+ color: var(--auth-primary);
+ font-size: var(--auth-control-size);
+ line-height: 18px;
+}
+.root .select .inputSurface {
+ justify-content: space-between;
+}
+.root .select .inputSurface > span:first-child {
+ min-width: 0;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ text-align: left;
+}
+.root .select:disabled {
+ opacity: var(--auth-disabled);
+}
+.root.menu {
+ width: var(--radix-popover-trigger-width, 280px);
+ max-width: calc(100vw - 32px);
+ padding: 4px;
+ z-index: 1000;
+ border: 1px solid var(--auth-border-subtle);
+ border-radius: 10px;
+ background: var(--auth-overlay);
+ backdrop-filter: blur(20px);
+ box-shadow:
+ 0 10px 15px -3px rgb(0 0 0 / 6%),
+ inset 0 1px 0 rgb(255 255 255 / 70%);
+}
+.root .options {
+ overflow-y: auto;
+ max-height: min(320px, var(--radix-popover-content-available-height, 60vh));
+}
+.root .menuRow {
+ @apply flex items-center justify-between;
+ min-height: 30px;
+ padding: 6px 8px;
+ gap: 8px;
+ border-radius: 6px;
+ color: var(--auth-primary);
+ font-size: var(--auth-control-size);
+ line-height: 18px;
+ cursor: pointer;
+ outline: none;
+ overflow-wrap: anywhere;
+}
+.root .menuRow[aria-selected="true"] {
+ background: var(--auth-selected);
+}
+.root .menuRow[data-focused="true"],
+.root .menuRow[data-highlighted] {
+ background: var(--auth-hover);
+}
+.root .menuRow[aria-disabled="true"] {
+ opacity: var(--auth-disabled);
+ cursor: not-allowed;
+}
+.root .menuSearch {
+ width: 100%;
+ border: 0;
+ border-bottom: 1px solid var(--auth-border-subtle);
+ padding: 6px 8px;
+ background: transparent;
+ font-size: var(--auth-input-size);
+ line-height: 18px;
+}
+.root .menuLabel,
+.root .empty {
+ padding: 6px 8px;
+ color: var(--auth-secondary);
+ font-size: var(--auth-caption-size);
+ line-height: 18px;
+ overflow-wrap: anywhere;
+}
+.root .accountTrigger {
+ @apply flex w-full items-center justify-between;
+ padding: 8px;
+ gap: 8px;
+ border: 0;
+ border-radius: 8px;
+ background: transparent;
+ color: var(--auth-primary);
+ text-align: left;
+ font-size: var(--auth-control-size);
+ line-height: 18px;
+ overflow-wrap: anywhere;
+}
+.root .accountTrigger:hover {
+ background: var(--auth-hover);
+}
+.root .accountTrigger:disabled {
+ opacity: var(--auth-disabled);
+}
+.root .notification {
+ position: fixed;
+ right: 20px;
+ bottom: 20px;
+ z-index: 1000;
+ width: max-content;
+ max-width: min(400px, calc(100vw - 40px));
+ padding: 12px;
+ border: 1px solid var(--auth-border-subtle);
+ border-radius: 10px;
+ background: var(--auth-overlay);
+ backdrop-filter: blur(20px);
+ box-shadow: 0 10px 15px -3px rgb(0 0 0 / 6%);
+}
+@keyframes spin {
+ to {
+ transform: rotate(360deg);
+ }
+}
+@keyframes pulse {
+ 50% {
+ opacity: 0.45;
+ }
+}
+@media (max-width: 767px) {
+ .root {
+ --auth-input-size: 16px;
+ --auth-touch-height: 44px;
+ --auth-group-gap: 24px;
+ }
+ .root .main {
+ flex: 0 0 auto;
+ align-items: flex-start;
+ padding: 24px 16px 0;
+ }
+ .root .button[data-variant="navigation"] .buttonSurface {
+ background: transparent;
+ color: var(--auth-primary);
+ padding: 0;
+ font-weight: 400;
+ line-height: 18px;
+ }
+ .root .button[data-variant="navigation"]:hover .buttonSurface {
+ background: transparent;
+ text-decoration: underline;
+ text-underline-offset: 3px;
+ }
+ .root .quietAction {
+ width: 44px;
+ height: 44px;
+ background: transparent;
+ }
+ .root .quietAction:hover {
+ background: transparent;
+ }
+ .root .quietAction::after {
+ content: "";
+ position: absolute;
+ inset: 8px;
+ border-radius: 9999px;
+ pointer-events: none;
+ }
+ .root .quietAction:hover::after {
+ background: var(--auth-hover);
+ }
+ .root .inputSurface:has(.quietAction) {
+ padding-right: 0;
+ }
+ .root .resourceActions {
+ grid-column: 2 / -1;
+ }
+ .root .resourceRow {
+ grid-template-columns: 24px minmax(0, 1fr);
+ }
+ .root .textLink {
+ min-height: 44px;
+ display: inline-flex;
+ align-items: center;
+ }
+ .root .labelRow .textLink {
+ min-height: 18px;
+ font-size: var(--auth-control-size);
+ }
+ .root .otpLane {
+ width: fit-content;
+ }
+ .root .footer {
+ padding: 11px 16px 40px;
+ }
+ .root .footer .textLink {
+ color: var(--auth-secondary);
+ }
+ .root .page:not(:has(.footer)) .main {
+ padding-bottom: 40px;
+ }
+}
+@media (prefers-reduced-motion: reduce) {
+ .root :where(*, *::before, *::after) {
+ animation: none;
+ transition: none;
+ }
+}
+/* biome-ignore lint/correctness/noUnknownMediaFeatureName: Standard reduced-transparency preference; unsupported browsers keep the fallback. */
+@media (prefers-reduced-transparency: reduce) {
+ .root.menu,
+ .root .notification {
+ background: #fafafa;
+ backdrop-filter: none;
+ }
+}
+.root [data-auth-titlebar] {
+ height: var(--desktop-title-bar-height, 0px);
+ flex-shrink: 0;
+ -webkit-app-region: drag;
+}
+.root .codeHelp {
+ text-align: center;
+ font-size: var(--auth-control-size);
+ line-height: 18px;
+ color: var(--auth-secondary);
+}
+.root .otpLane:has(input[aria-invalid="true"]) .otpSlot {
+ border-color: var(--auth-error);
+}
+
+.root .accountEmail {
+ display: block;
+ color: var(--auth-secondary);
+ font-size: var(--auth-caption-size);
+}
+
+.root .readonlyValue {
+ font-family: var(--auth-mono);
+}
diff --git a/packages/auth-ui/src/utilities.css b/packages/auth-ui/src/utilities.css
new file mode 100644
index 00000000000..46e6547c9ca
--- /dev/null
+++ b/packages/auth-ui/src/utilities.css
@@ -0,0 +1,25 @@
+@layer theme, base, components, utilities;
+@import 'tailwindcss/theme.css' layer(theme) prefix(au);
+@import 'tailwindcss/utilities.css' layer(utilities) prefix(au) source(none);
+@source '../../../apps/sim/app';
+@source '../../../apps/sim/ee/sso/components/sso-auth.tsx';
+@source '../../../apps/sim/ee/sso/components/sso-form.tsx';
+@source '../../../apps/desktop/src/renderer';
+@theme {
+ --spacing: 4px;
+ --text-title: 24px;
+ --text-title--line-height: 32px;
+ --text-body: 14px;
+ --text-body--line-height: 22px;
+ --text-control: 13px;
+ --text-control--line-height: 18px;
+ --text-caption: 12px;
+ --text-caption--line-height: 18px;
+ --text-md: 16px;
+ --text-md--line-height: 24px;
+ --radius-lg: 8px;
+}
+@theme inline {
+ --font-sans: var(--auth-font);
+ --font-mono: var(--auth-mono);
+}
diff --git a/packages/auth-ui/src/wordmark.tsx b/packages/auth-ui/src/wordmark.tsx
new file mode 100644
index 00000000000..7130125ca0e
--- /dev/null
+++ b/packages/auth-ui/src/wordmark.tsx
@@ -0,0 +1,27 @@
+/** Canonical Sim logotype outlines from the brand guide. */
+const WORDMARK_VIEW_BOX = { width: 441, height: 212 } as const
+
+/** Glyph outlines, drawn in `WORDMARK_VIEW_BOX` coordinates. */
+const WORDMARK_PATHS: readonly string[] = [
+ 'M0 160.9H29.51C29.51 169.08 32.46 175.61 38.37 180.48C44.27 185.12 52.25 187.44 62.31 187.44C73.24 187.44 81.65 185.34 87.56 181.14C93.46 176.71 96.41 170.85 96.41 163.55C96.41 158.24 94.77 153.82 91.49 150.28C88.43 146.74 82.75 143.86 74.44 141.65L46.24 135.01C32.03 131.47 21.42 126.05 14.43 118.75C7.65 111.45 4.26 101.83 4.26 89.88C4.26 79.93 6.78 71.3 11.81 64C17.05 56.7 24.16 51.06 33.12 47.08C42.3 43.09 52.8 41.1 64.6 41.1C76.41 41.1 86.57 43.2 95.1 47.41C103.84 51.61 110.62 57.47 115.43 64.99C120.46 72.52 123.08 81.48 123.3 91.87H93.79C93.57 83.47 90.84 76.94 85.59 72.3C80.34 67.65 73.02 65.33 63.62 65.33C54 65.33 46.57 67.43 41.32 71.63C36.07 75.83 33.45 81.59 33.45 88.89C33.45 99.73 41.32 107.14 57.06 111.12L85.26 118.09C98.81 121.19 108.98 126.28 115.76 133.35C122.53 140.21 125.92 149.61 125.92 161.56C125.92 171.74 123.19 180.7 117.73 188.44C112.26 195.96 104.72 201.82 95.1 206.03C85.7 210.01 74.55 212 61.65 212C42.85 212 27.87 207.35 16.72 198.06C5.57 188.77 0 176.38 0 160.9Z',
+ 'M232.8 212H202.13L202.13 49.76H229.54V77.39C232.8 68.34 239.11 60.66 247.81 54.7C256.73 48.52 267.5 45.43 280.12 45.43C294.26 45.43 306.01 49.29 315.36 57.02C324.72 64.75 330.81 75.01 333.64 87.82H328.09C330.27 75.01 336.25 64.75 346.04 57.02C355.83 49.29 367.9 45.43 382.26 45.43C400.54 45.43 414.89 50.84 425.34 61.66C435.78 72.47 441 87.26 441 106.03V212H410.98V113.65C410.98 100.84 407.71 91.02 401.19 84.17C394.88 77.11 386.29 73.58 375.41 73.58C367.79 73.58 361.05 75.34 355.17 78.88C349.52 82.19 345.06 87.04 341.8 93.45C338.53 99.85 336.9 107.36 336.9 115.97V212H306.55V113.32C306.55 100.51 303.4 90.8 297.09 84.17C290.78 77.33 282.19 73.91 271.31 73.91C263.69 73.91 256.95 75.67 251.08 79.21C245.42 82.52 240.96 87.38 237.7 93.78C234.43 99.96 232.8 107.36 232.8 115.97V212Z',
+ 'M184.83 20.55C184.83 31.9 175.64 41.1 164.29 41.1C152.95 41.1 143.76 31.9 143.76 20.55C143.76 9.2 152.95 0 164.29 0C175.64 0 184.83 9.2 184.83 20.55Z',
+ 'M179.43 212H149.16V49.76C153.76 51.91 158.88 53.12 164.29 53.12C169.7 53.12 174.83 51.91 179.43 49.76V212Z',
+]
+
+/** Brand-guide outlines, rendered with Auth-owned size and ink. */
+export function AuthWordmark() {
+ return (
+
+ {WORDMARK_PATHS.map((d) => (
+
+ ))}
+
+ )
+}
diff --git a/packages/auth-ui/tsconfig.json b/packages/auth-ui/tsconfig.json
new file mode 100644
index 00000000000..6e7e485cdc6
--- /dev/null
+++ b/packages/auth-ui/tsconfig.json
@@ -0,0 +1,6 @@
+{
+ "extends": "@sim/tsconfig/library.json",
+ "compilerOptions": { "jsx": "react-jsx", "lib": ["ES2022", "DOM", "DOM.Iterable"] },
+ "include": ["src/**/*"],
+ "exclude": ["node_modules", "dist"]
+}
diff --git a/packages/emcn/package.json b/packages/emcn/package.json
index 734ca605ac8..31d3c8db466 100644
--- a/packages/emcn/package.json
+++ b/packages/emcn/package.json
@@ -62,7 +62,6 @@
"@tanstack/react-virtual": "^3.13.24",
"class-variance-authority": "^0.7.1",
"framer-motion": "^12.5.0",
- "input-otp": "^1.5.0",
"next": ">=15",
"prismjs": "^1.30.0",
"react": "^19",
@@ -89,7 +88,6 @@
"@types/react-dom": "^19",
"class-variance-authority": "0.7.1",
"framer-motion": "^12.5.0",
- "input-otp": "1.5.0",
"next": "16.4.1",
"prismjs": "^1.30.0",
"react": "19.2.4",
diff --git a/packages/emcn/src/components/index.ts b/packages/emcn/src/components/index.ts
index d38342fd34e..5beb7c4d08b 100644
--- a/packages/emcn/src/components/index.ts
+++ b/packages/emcn/src/components/index.ts
@@ -44,7 +44,7 @@ export {
type ChipDropdownProps,
} from './chip-dropdown/chip-dropdown'
export { ChipEmailsInput } from './chip-emails-input/chip-emails-input'
-export { ChipInput, type ChipInputProps } from './chip-input/chip-input'
+export { ChipInput } from './chip-input/chip-input'
export {
ChipConfirmModal,
type ChipConfirmTextSegment,
@@ -112,7 +112,6 @@ export {
InfoCardList,
} from './info-card/info-card'
export { Input } from './input/input'
-export { InputOTP, InputOTPGroup, InputOTPSlot } from './input-otp/input-otp'
export { Label } from './label/label'
export { Lightbox } from './lightbox/lightbox'
export {
diff --git a/packages/emcn/src/components/input-otp/input-otp.tsx b/packages/emcn/src/components/input-otp/input-otp.tsx
deleted file mode 100644
index 128d9b5abbd..00000000000
--- a/packages/emcn/src/components/input-otp/input-otp.tsx
+++ /dev/null
@@ -1,95 +0,0 @@
-/**
- * An OTP input component matching the emcn design system.
- *
- * Wraps the `input-otp` library with emcn design tokens for consistent styling.
- *
- * @example
- * ```tsx
- * import { InputOTP, InputOTPGroup, InputOTPSlot } from '../../index'
- *
- *
- *
- *
- *
- *
- *
- *
- *
- *
- *
- * ```
- *
- * @see InputOTP - Root component wrapping OTPInput
- * @see InputOTPGroup - Groups slots together
- * @see InputOTPSlot - Individual digit slot
- */
-'use client'
-
-import * as React from 'react'
-import { OTPInput, OTPInputContext } from 'input-otp'
-import { cn } from '../../lib/cn'
-
-/**
- * Root OTP input component. Manages the overall input state and layout.
- */
-const InputOTP = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
->(({ className, containerClassName, ...props }, ref) => (
-
-))
-InputOTP.displayName = 'InputOTP'
-
-/**
- * Groups OTP slots together with consistent spacing.
- */
-const InputOTPGroup = React.forwardRef<
- React.ElementRef<'div'>,
- React.ComponentPropsWithoutRef<'div'>
->(({ className, ...props }, ref) => (
-
-))
-InputOTPGroup.displayName = 'InputOTPGroup'
-
-/**
- * Individual OTP digit slot. Displays the entered character and a fake caret when active.
- *
- * Uses emcn design tokens for consistent styling with the Input component.
- */
-const InputOTPSlot = React.forwardRef<
- React.ElementRef<'div'>,
- React.ComponentPropsWithoutRef<'div'> & { index: number }
->(({ index, className, ...props }, ref) => {
- const inputOTPContext = React.useContext(OTPInputContext)
- const { char, hasFakeCaret, isActive } = inputOTPContext.slots[index]
-
- return (
-
- {char}
- {hasFakeCaret && (
-
- )}
-
- )
-})
-InputOTPSlot.displayName = 'InputOTPSlot'
-
-export { InputOTP, InputOTPGroup, InputOTPSlot }
diff --git a/scripts/test-patterns-baseline.json b/scripts/test-patterns-baseline.json
index 0d7ed8685bb..a21ba5665e6 100644
--- a/scripts/test-patterns-baseline.json
+++ b/scripts/test-patterns-baseline.json
@@ -44,7 +44,6 @@
"local-factory\tapps/sim/connectors/confluence/attachments.test.ts\t@/lib/knowledge/documents/secure-fetch.server",
"local-factory\tapps/sim/ee/access-requests/components/permission-access-boundary.test.tsx\t@sim/emcn",
"local-factory\tapps/sim/ee/credential-groups/components/organization-account-workspace-access.test.tsx\t@sim/emcn",
- "local-factory\tapps/sim/ee/sso/components/sso-form.test.tsx\t@sim/emcn",
"local-factory\tapps/sim/ee/sso/components/sso-settings.test.tsx\t@sim/emcn",
"local-factory\tapps/sim/executor/execution/block-executor.test.ts\t@/lib/logs/execution/pii-redaction",
"local-factory\tapps/sim/executor/utils/output-filter.test.ts\t@/blocks",