Release v1.1.0 - #105
Merged
Merged
Conversation
The console single-admin lock hard-refused a non-owner at the OAuth callback (admin_locked), so once the owner withdrew their cloud account the console was effectively bricked: a new account could not even learn who owned it or switch. Separately the local session was validated only by its 12h TTL, so a withdrawn owner kept reporting logged-in from the cached snapshot. - login.go: a non-owner login now succeeds (soft block) and receives a console session, but the org-admin registrar runs only for the owner, so a non-owner never takes over admin authority. Remove failOwnerLocked and the admin_locked bounce. - service.go: GET /console/session revalidates a live session against the cloud (GET /me). A 401 (nil principal, nil error) is the signal a withdrawn account produces and expires the local login on the spot. The response reports is_owner (+ owner_email) so the app can gate. - frontend: RequireAuth shows a new OwnerLockedNotice for is_owner:false instead of the admin shell; TSession carries is_owner/owner_email. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…constraint react-router@8.2.0 declares engines.node >=22.22.0, so a fresh install on Node 20 fails with EBADENGINE/notsup despite our engines field promising >=20 works. Align the declared engine with what the dependency tree actually requires. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
fix(frontend): require Node >=22.22.0 to match react-router@8 engine …
Move every user-visible Korean string into src/locales (ko.ts verbatim, en.ts translated). The locale is chosen once at page load (localStorage rc_lang override, else browser language) and exported as L; components read plain constants — no i18n library, no runtime lookup. ko.ts is typed against en.ts's shape so the two languages cannot drift apart silently. Parameterized strings are per-language functions so Korean counters and English word order each read naturally. A globe LanguageToggle (labeled with the language it switches to) sits in both navbars — signed-in and signed-out — and persists + reloads. Tests are pinned to Korean in setup.ts since the suite asserts the original copy; locales.test.ts covers the English side and structural parity. Dev-only fixtures (UITestPage, teamsDummyData) and code comments intentionally keep their Korean. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Normalize brand 'Rune'/'rune' → 'RUNE' in the en/ko locale tables (account-required, invite status, update notice). Technical identifiers are unaffected (none present in the locale files).
Drive language state with plain i18next; L becomes a Proxy over the active locale table so all L.x.y call sites and the compile-time-typed tables are unchanged. A LocaleProvider subscribes to i18next's languageChanged and remounts its subtree, so switching re-renders in place instead of window.location.reload() — removing the switch flash. setLanguage calls changeLanguage (keeps localStorage persistence); LanguageToggle reads the live language via getLanguage(). Locale test compares L by structure since it is now a Proxy.
CI installs with --frozen-lockfile; add the i18next entry so the lockfile matches package.json.
- Delete teamsDummyData.ts (468 lines, zero imports) and its orphaned wire-type copies - Delete unused alertStore + test; its role is served by noticeStore (drop now-orphaned TAlert type) - Drop dummy team ids (t_a/t_e) from TreeDetailView fallback selection and default expansion — they never match real API data - Route /ui-test only in dev builds via a DEV-gated lazy import so the 1,084-line showcase page and its chunk stay out of production Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…py in constants Add three constants modules as single sources for strings that were hardcoded per file, so a wording or wire-value change is a one-line edit and typos become compile errors: - constants/apiConstants.ts — wire-contract vocabulary (INVITATION_STATUS, SESSION_STATUS, WORKSPACE_STATUS, SYSTEM_UPDATE_STATE, TEAM_MEMBER_ROLE, ERROR_CODES); the matching union types are now derived from these objects, and the status→label maps in styleConstants are pinned with satisfies so a vocabulary change fails compilation until the labels follow - constants/errorConstants.ts — error-code→copy maps (TEAM_REASON, BATCH_REASON, ADD_MEMBER_REASON, BATCH_REASON_FALLBACK), replacing five per-file re-declarations; the duplicate-team-name copy is shared with the create/rename modals' client-side check. Unmapped-code fallback behavior is unchanged and now documented per call site - constants/noticeConstants.ts — showNotice copy per flow (NOTICE_TEXT, 11 flows, 20 call sites), collapsing cross-file duplicates (resend invitation, create team, remove membership); titles reference MODAL_TITLES where the notice reports that modal's action Also add DEFAULT_PAGE_SIZE to commonConstants (was declared as PAGE_SIZE=10 in three files) and drop UITestPage's duplicate ROLE_OPTIONS in favor of the teamOptions export. MemberStatus chip props stay literal on purpose — they are chip vocabulary behind the CHIP_STATUS seam, not wire values. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…e build
MemberDetailDrawer copied user.memberships into useState once on mount,
so the fresher GET /users/{id} payload and every post-mutation refetch
were silently ignored in favor of locally patched rows. Server truth
now flows straight from props and the drawer stages only the user's own
edits (pendingRoles map + checkedIds set), re-applied as a diff on top
of whatever the server currently says — the manual state mirroring
after add/remove/role mutations is gone, since those mutations already
invalidate the user detail/list queries. Batch partial-failure behavior
is unchanged: failed targets keep their staged edits for a retry.
Adds a regression test: refetched memberships render without a remount
and staged picks survive.
TreeDetailView rebuilt the whole team tree on every render (each
keystroke/checkbox/staged edit) with an O(n^2) filter-per-parent scan.
flatById/teamNodes are now memoized on the teams array and the build
runs as a single pass over a children index; ancestorIds reuses the
memoized map instead of building its own per render.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…s, and page titles Extend commonConstants with the remaining repeated UI copy so a wording change lands in one place: - PAGE_TITLES — page/section vocabulary; the nav, every page's section aria-label, and MODAL_TITLES.workspaceManage now derive from it - TABLE_HEADERS — column headers shared by list tables, modal tables (ModalTable head arrays + hand-rolled th), and sort-option labels. TreeDetailView's 역할 vs 권한 drift is kept verbatim as roleAlt with a comment pending a copy decision - INPUT_LABELS / PLACEHOLDERS — invite & add-member form copy (팀 선택 4x, 권한 선택 4x, 추가할 팀 없음, ...) - ARIA_LABELS — fixes the 전체선택/전체 선택 drift to one spelling - FEEDBACK_TEXT — the refresh-retry line repeated on three pages Add utils/email.ts (EMAIL_PATTERN + EMAIL_FORMAT_ERROR) following the username.ts pattern-with-copy convention — the regex was declared twice and the error copy had already drifted on punctuation; both modals now share one with-period version. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… hooks
Add the UI-state hook layer the pages were missing (the reference
project's useXxxForm/behavior-hook pattern) and point all consumers at
it:
- usePageScopedSelection — the Set-of-ids checkbox selection duplicated
across UsersPage, TreeDetailView, and MemberDetailDrawer (toggleOne/
toggleAll/clearSelection + setSelectedIds for batch reconciliation)
- useServerPagination — one pagination state machine replacing three
divergent implementations, standardized on the strictest variant:
totalPages tracks the last response as state and the returned page is
clamped BEFORE the query call, so an out-of-range request never fires.
Fixes UsersPage passing the raw (unclamped) page to the query for one
cycle, and gives TreeDetailView clamping it never had. Callers report
totals back via useSyncPaginationTotal
- useBatchFailureModal + toBatchFailureRows — the partial-failure modal
state and failed→{label, reason} mapping repeated five times across
three files; the per-flow fallback policy (generic retry copy vs raw
code) stays a caller choice
Each hook ships with unit tests (14 new).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…re table state rows - Delete teams/RoleChangeConfirmModal and teams/RemoveMembershipModal — TreeDetailView now uses the users versions, which were designed for reuse (subjectLabel/targets props, ModalTable). Kills the hand-rolled duplicate modal-table styles and the th padding drift (py-2 vs py-1.5). Intended UX change on SC-06: role-change/removal results now render inside the confirm modal (wireframe v0.12 states E-1/E-2), matching the SC-13 drawer; partial-failure handling and staged-edit retention are unchanged. Tests updated to the new contract - Add MODAL_STYLE_VAR (message/body/footer/footerCompact) to styleConstants and replace 27 class literals across 12 modal files; resolves the body gap-4/gap-5 drift. The two footer spacings in active use are kept as named variants pending a design pass - Add TableLoadingRow/TableEmptyRow beside TableErrorRow and replace the inline state rows in SessionsPage/UsersPage/TreeDetailView, resolving the py-6/py-8 and text-faint/text-muted-foreground drift - Drop the now-orphaned NOTICE_TEXT.roleChange entry and removeMembership.failure (both render in-modal now) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Pure moves/renames — no runtime behavior change: - Single store home: src/stores/* → src/state/store/ (reference convention), stores/ folder removed, 20 import sites updated - Resolve the TTeamNode name collision: the recursive UI tree node is now TTeamViewNode in teamTypes; TTeamNode remains only the flat wire row, so the same name can no longer resolve to two shapes - Split workspace types out of commonTypes into types/workspaceTypes; commonTypes keeps only genuinely cross-cutting shapes - Relocate non-component .ts out of components/: teamHierarchy and invitePreview → utils/; teamOptions split into constants/teamConstants (pattern/rule text/ROLE_OPTIONS) + utils/buildTeamOptions; memberStatusMap → constants/userConstants - Fold single-file folders: drawer/MembershipRow → components/users (its only consumer domain), toast/ToastContainer → components/elements (reference keeps Toast in elements) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…onents 720-line component → 428-line composition plus five focused modules, behavior preserved (all existing tests pass unchanged): - hooks/useStagedRoleEdits — the staged role-edit machine (pendingRoles/ savedRoles, un-stage on picking the base value back, partial-failure reconciliation committing only what succeeded) - hooks/useTeamCrud — create/rename/delete orchestration with the TEAM_REASON error mapping and success notices; TeamsPage's duplicated empty-state create flow now uses it too, removing the last near-verbatim logic duplication between the two files - components/teams/TeamCard, TeamMembersToolbar, TeamMembersTable — pure views; staging/selection state stays in the parent's hooks - buildTeamNodes/findTeamNode/ancestorIds move to utils/teamHierarchy beside the existing tree lookups Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ooks and views Behavior preserved (all existing tests pass unchanged): - hooks/useMembershipDrafts — the SC-13 membership machine extracted from the drawer: props-derived rows with diff staging (pendingRoles/ checkedIds), the add-team flow, and the role-change/removal confirm reconciliation incl. the batch-failure surface. The two 25-line async confirm closures that lived inside modal JSX become one-line handler references - components/users/MembershipSection — the 소속 팀 block (table + action bar + add-picker row) as a pure view - hooks/useUserBatchActions — the SC-11 bulk flows (invite/resend/ batch delete) with their notice/batch-failure wiring; selection reconciliation and drawer close-out stay caller-injected - components/users/UsersToolbar, UserRow — pure views; membershipSummary moves next to its only consumer Drawer: 660 → 362 lines, UsersPage: 580 → 370. Every page component is now composition over the shared hook layer. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…pages - formatDate constructed a new Intl.DateTimeFormat on every call — one of the costlier Intl operations, running in every table timestamp cell on every render. The formatter options never change, so it is now a module-level constant - Pagination rendered one button per page unbounded; the session history table grows without bound, so hundreds of pages meant hundreds of buttons. It now shows a sliding 5-page window centered on the current page, clamped at both ends so the button count never jumps while paging (1→[1..5], 4→[2..6], 9 of 10→[6..10]); page counts of 5 or fewer render unchanged. Window/clamp regression tests added Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The team/workspace confirm modals spaced their button rows with gap-2 while the users flows used gap-4 — since the modal-fork unification both spacings could appear on the same screen (SC-06). Drop the footerCompact variant and point its seven call sites at MODAL_STYLE_VAR.footer (gap-4, items-center). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Module-scope constants that captured L.* froze at the load-time language,
because LocaleProvider's key={lang} remount re-runs render but not module
top-level code. Make the shared containers resolve per-access and inline
the rest at their render sites:
- styleConstants: MEMBER/INVITATION/WORKSPACE_STATUS_VAR labels via a
statusVar() getter helper (status badges)
- commonConstants: BTN_TEXT/MODAL_TITLES as forwarding proxies over
L.btn/L.modal; NAV_LIST titles as getters (buttons, modal titles, nav)
- inline L.* at render sites for team-name hint, invite/delete/role/
membership messages, username error; batchReason() helper in the drawer
Phase 1 of the i18n re-integration. release/v1.0.6 refactored the same components this branch localized (decomposed drawers/pages into hooks+ views, unified forked modals, centralized copy into constants), so the 28 conflicts were resolved by taking release's version wholesale — the newer architecture is the baseline. The i18n-only files (locales/en.ts, ko.ts, index.ts, LocaleProvider, LanguageToggle) are non-conflicting and survive. Two forked modals release deleted (teams/RemoveMembershipModal, teams/RoleChangeConfirmModal) are dropped to match. Phase 2 (follow-up commit) re-wires the i18n layer onto release's centralized copy and localizes the new hook/view files. The tree is intentionally un-localized (release Korean) at this commit.
Re-integrate the i18n layer onto release/v1.0.6's architecture, starting with the highest-leverage surface — the centralized copy constants, which cascade to every screen: - mount LanguageToggle in the release Navbar; localize the workspace badge - commonConstants: BTN_TEXT, PAGE_TITLES, MODAL_TITLES, TABLE_HEADERS, INPUT_LABELS, PLACEHOLDERS, ARIA_LABELS, FEEDBACK_TEXT, NAV_LIST → live getters/functions over L.* - noticeConstants, errorConstants (computed-key getters), styleConstants (status badges via statusVar getters) → live over L.* - teamConstants/email/username bare exports inlined at call sites (live) All copy re-resolves on a live switch (getters/proxies), so no reload flash. Component-level inline strings (pages, modal bodies) are the remaining pass. tsc clean, 333/333 tests pass, build succeeds.
Complete the re-integration: every user-visible Korean string in the components/pages now reads from the locale tables, so a live language switch turns the entire UI English (not just the shared chrome). - 25 files auto-localized (JSX text, attributes, string props) via the Korean→key reverse index from ko.ts - interpolated strings wired to the locale's function keys (footers, aria-labels, confirm-dialog bodies, member-count headings, team meta, invite notices with the embedded status chip) - inlined the remaining frozen module-const message captures so they re-resolve live (send/remove/role/delete failure copy) Only code comments and regex character classes retain Korean. tsc clean, 333/333 tests pass, build succeeds.
The Users and Sessions filter/sort option arrays were module-scope constants whose labels read L.* / getters — so they froze at the load-time language and the dropdowns stayed in that language after a live switch (the rest of the page switched, leaving the page looking half-translated). Convert them to builder functions called in the component body so they re-resolve on every render. tsc clean, 333/333 tests pass, build succeeds.
The mock server seeds English sample team names (Platform, Data, …). Add a demo-only L.demoTeams map + localizeTeamName() so those sample names follow the language toggle on the Team Management surfaces (tree, org chart, team card, pickers). Real, customer-created names are not in the map and render verbatim — team names are user data and must never be auto-translated in production; drop this map when the app talks to a real backend. Also aligns nav.sessions to the wireframe (세션 관리 / Session Management). The delete-confirm tests type the displayed (localized) name. tsc clean, 333/333 tests pass, build succeeds.
UI english versions
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
jh-lee-cryptolab
force-pushed
the
release/v1.1.0
branch
from
August 13, 2026 08:08
937cdb3 to
ab3f8b6
Compare
redcourage
approved these changes
Aug 13, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
포함 내용
기능
RUNE로 통일리팩터
수정
성능과 스타일
What's included
Features
RUNERefactors
Fixes
Performance and style