webapp/package.json- Current dependencies: React 18, Redux 4, react-redux 7, React Router 5, react-beautiful-dnd 13, formik 2, yup 0.32, uuid 8, react-onclickoutside 6, react-markdown 6, typesafe-actions 5, reselect 4webapp/tsconfig.json- target es2020, strict:true, jsx:react-jsx, moduleResolution:bundlerwebapp/vite.config.ts- Vite 5, builds tobuild/, dev on port 3000webapp/index.html- Loads Tailwind from CDN, Material Icons CDN, Google Fonts CDN, Stripe.js CDNwebapp/src/index.tsx(lines 1-15) - Uses deprecatedReactDOM.renderinstead ofcreateRootwebapp/src/App.tsx(lines 1-52) - Class component with React Router v5<Switch>andconnect()webapp/src/configureStore.ts(lines 1-11) - ManualcreateStorewith deprecated Redux DevTools extension patternwebapp/src/store/index.ts(lines 1-47) - 9 domain reducers combined withcombineReducers, hand-writtenAllActionsunion typewebapp/src/api/index.ts(~800 lines) - ~80 rawfetch()API functions with duplicatedresponse.json().then()patternswebapp/src/components/elements.tsx- Uses deprecatedSFCtype, nochildreninCardLayoutpropswebapp/src/components/Board.tsx(842 lines) - Largest component, class-based, react-beautiful-dnd, inline API calls, 8 useState equivalents asthis.statewebapp/src/components/EntityDetailsBody.tsx(725 lines) - Class component, ~30alert()error calls, inline API calls,window.confirm()webapp/src/components/EntityDetailsTitle.tsx(319 lines) - Formik render-prop, react-onclickoutside HOCwebapp/src/store/application/actions.ts- Manual action types enum, typesafe-actions, dispatch thunkswebapp/src/store/features/reducers.ts- Switch-case reducer with manual state spreadingwebapp/src/store/features/selectors.ts- reselect createSelector usagewebapp/src/core/misc.ts- Color/misc enums, annotations, ~250 lines of country datawebapp/src/core/card.ts- EntityTypes union type for polymorphic card handling
// actions.ts
export enum ActionTypes { CREATE_FEATURE = 'CREATE_FEATURE', ... }
export interface createFeature { type: ActionTypes.CREATE_FEATURE, payload: IFeature }
export const createFeatureAction = (x: IFeature) => action(ActionTypes.CREATE_FEATURE, x)
// reducers.ts
export function reducer(state = initialState, action: Actions) {
switch (action.type) {
case ActionTypes.CREATE_FEATURE: return { ...state, items: [...state.items, action.payload] }
}
}// Every component uses this pattern:
class X extends Component<Props, State> {
constructor(props) { super(props); this.state = { ... } }
componentDidMount() { API_FETCH_X().then(r => r.json().then(data => ...)) }
render() { return (<div>...</div>) }
}
export default connect(mapStateToProps, mapDispatchToProps)(X);// ~80 functions in api/index.ts, all raw fetch()
export const API_RENAME_FEATURE = async (workspaceId, id, title) =>
await fetch(endpoint + "/features/" + id + "/rename", {
method: 'POST', headers: { 'Workspace': workspaceId }, credentials: 'include',
body: JSON.stringify({ title })
});
// Components call these inline with .then().catch(), ~40 alert() error handlers// React Router v5
<Switch>
<Route exact path="/account/login" component={LoginPage} />
<Route path="/" component={IndexPage} />
</Switch>
// Pages access: this.props.match.params, this.props.history.push()webapp/src/
api/index.ts -- ~80 raw fetch functions, no service layer
App.tsx, App.css -- root layout, React Router v5 Switch
index.tsx -- entry: ReactDOM.render + Provider + BrowserRouter
configureStore.ts -- manual createStore with compose
store/
index.ts -- combineReducers, AllActions type
application/ -- app state, messages system
features/ -- feature cards CRUD + drag move
milestones/ -- milestone columns CRUD + reorder
projects/ -- project CRUD
workflows/ -- workflow rows CRUD + reorder
subworkflows/ -- subworkflow groups within workflows
personas/ -- persona management
workflowpersonas/ -- persona-workflow assignments
featurecomments/ -- comments on features
components/
Board.tsx -- main board: DnD context, milestone/workflow/subworkflow/feature grid
Card.tsx -- feature card display (Link wrapper)
EntityDetailsBody.tsx -- entity detail: CRUD, color, annotations, estimate, delete
EntityDetailsTitle.tsx -- inline editable title (Formik)
EntityDetailsDescription.tsx -- inline editable description (Formik)
EntityDetailsComments.tsx -- comment list + new comment form (Formik)
EntityDetailsAnnotations.tsx -- annotation tags (Formik)
EntityDetailsModal.tsx -- modal wrapper
CreateCardModal.tsx -- create feature/milestone/workflow/subworkflow (Formik)
CreateProjectModal.tsx -- create project (Formik)
CreateWorkspaceModal.tsx -- create workspace (Formik)
Personas.tsx -- persona bar: create/edit/assign (Formik)
ContextMenu.tsx -- generic dropdown menu (onClickOutside HOC)
Header.tsx, Footer.tsx, Messages.tsx -- layout chrome
elements.tsx -- Button, CardLayout primitives
Comment.tsx -- single comment display + inline edit
NewCard.tsx, NewDimCard.tsx, EmptyCard.tsx -- simple display components
pages/
IndexPage.tsx -- app loader, auth gate, sub-routing
ProjectPage.tsx -- project detail, loads all entities, renders Board
WorkspacePage.tsx -- workspace with project list + sub-routing
...auth pages... -- Login, SignUp, ResetPassword, VerifyEmail, Logout, AcceptInvite
...settings pages...-- AccountPage, WorkspaceSettingsPage
core/
lexorank.ts, lexorank.test.tsx -- lexicographic ranking for drag reorder
misc.ts -- colors, annotations, countries, role helpers
card.ts -- EntityTypes union type
IndexPagefetches/account/app-> dispatchesreceiveAppAction-> loads workspaces/memberships/accountWorkspacePagefetches projects for workspace -> dispatchesloadProjectsActionProjectPagefetches all project entities (milestones, workflows, features, comments, personas) -> dispatches load actions for each domainBoardrenders the grid, handles drag-and-drop with optimistic Redux dispatches + async API calls- Entity detail modals do inline API calls with
alert()on failure, dispatchupdateXActionon success - Forms use Formik render-prop pattern with Yup validation schemas
Open webapp/TODO.md for the full modernization plan. The recommended first action is Phase 1.1 (bump Vite to 6) combined with Phase 1.2 (createRoot migration in src/index.tsx), as these are low-risk and unlock everything else.
The most impactful single task is Phase 2.3 (convert Redux stores to RTK createSlice), which eliminates ~500 lines of boilerplate across 9 domains and makes Phase 3 (component migration) cleaner by enabling useAppDispatch/useAppSelector hooks.