Lessions from AI coding
Design
Architecure
-
Client Application
-
Unified data schema across the whole application, including:
- Database (file-based) models and configuration files
- API payloads
- Client-side type definitions
-
Event-based propagation for notifications across the whole app:
- A central event bus is used to send and receive events.
- Components can subscribe to specific events and react without being directly coupled to the event source.
-
Hot reload for seamless updates:
- Modules and configuration can be updated on-the-fly without restarting the entire application.
- This significantly improves the development workflow and allows for dynamic adjustments in production.
-
Clear directory hierarchy for different packages (ui/functions/versioned api):
- No mix or overlap
- Grouped by function
-
-
Web Application
-
Versioned API with a clear, predictable hierarchy: align API routes with the source code and package structure so routes reflect implementation and versioning.
- Group similar endpoints under a unified sub-path (e.g., /api/v1/users/, /api/v1/products/) to improve discoverability, routing consistency, and isolated versioning.
-
UI hierarchy aligned with code/package structure
-
UI/UX
Google Stitch is a good tool for design. As I don’t have practical experience with Figma, I can talk with Stitch to convey the requirements, and it can create up to 3 drafts. Stich Now has a redesign feature: you can paste a screenshot of the current design, and it can improve your design for you. If you are satisfied with the result, you can select the design and ask Stitch to prototype; then it can create an interactive UI for you. And of course, you can export it to Google AI Studio or copy it to the clipboard.
UI/UX design is not isolated from the rest of the architecture; it must be considered as part of the whole system.
- If a UI component can be used in more than one place, create it as a shared component.
- Prefer shadcn/ui and Tailwind CSS—not just for aesthetics, but because they are easier for AI-assisted development to reason about. Tailwind uses human-readable utility names rather than opaque technical terms, which suits AI coding well.
Authentication and Authorization
In the past 2 weeks, I am working on the MaCo MTool dashboard. Initially, I developed it as a monolithic application, including frontend/auth-service/backend-service as one big app. Since Fred pushed this tool to other teams, managers acknowledged that we still need this tool. We will make it an official tool across the MaCo team, so I have to split them to improve potential performance once we add more features. So I split this app.
-
Permission design
-
Permissions for each API call; each has
Read (GET),Write (POST/PUT), andDelete (DEL), so there is a long list of permissions seeded to the database when initializing the database. -
Group into 3 global roles
Adminrole: it has all permissions.EditorhasRead and Writepermissions.ViewerhasReadpermissions.
-
User / resource groups
- Add users to the groups.
- Add resources to the groups.
- Users in the group can access the resources in that group.
- Admin users will bypass the group constraints.
The frontend uses Next.js 16 and better-auth. Initially I used next-auth, but it is coupled with Next.js and cannot run in a standalone service, so I switched to better-auth, which supports multiple authentication methods and email/phone as authentication.
-
-
Authorization with
custom-sessionWhen a user makes each service call to the backend, it will call
auth-service/api/v1/auth/get-session, which will return user info plus roles and permissions, but actually it only returns theroleId, and we have two levels of permissions. The defaultget-sessioncan’t provide such group-level info, so we need to use acustom-sessionplugin, so the whole response contains the detailed permission list.const auth = betterAuth({// ...other configplugins: [// ...other pluginscustomSession(async ({ user, session }) => {// enrich session with roles/permissions/groups})]})And in the backend service, when it receives any request, it first verifies the permission and group to determine if it will be rejected as 403 or continue processing. In that group scope, do the operations without breaking the boundaries.
Database
-
When user info is stored in a
Usertable, and other entities such as aproducttable also reference theuserIdoruserNamefrom theUsertable, but we can’t allow users to have authority to list all users, how to do this?My consideration:
-
Store the auth info and the business logic in the same database.
-
When any user creates the entity, it will save the
userIdanduserNamealong with the entity in the table. Other users querying the entity can always know who created this entity, but there may be inconsistencies when the creator changes their name, but currently it should be OK. -
If auth and business are in separate databases, we can also sync the user table to the business table.
- Shows current names
- No user:read permission needed for regular users
- Fast - local DB lookup
- Handles renames - webhook updates cache
- Ready for separate DBs
- Industry-standard pattern
And auth-servce to backend-service communicates via HMAC (Hash-based Message Authentication Code) which is is a cryptographic mechanism to verify:
- Authenticity - The request came from a trusted source (auth-service)
- Integrity - The payload wasn’t tampered with in transit
- the sync process will also use HAMC to do the data sync
-
Code Rules
-
I’ve collected many rules; they’re integrated into super-dev-plugin and my CLAUDE.md, and I will continue improving them.
-
For variables, define them in advance and use clear, human-readable names that describe their purpose and function. Follow consistent naming conventions, limit scope to the smallest necessary, prefer immutable constants where applicable, and document any non-obvious choices or side effects.
git worktree
We can use git worktree to implement a feature. Since we are human and a design may not be fully covered, especially if a feature is more complicated, we can use a git worktree to do a draft implementation. If there are any issues, we can do new research and assessment, update the spec, and create a new worktree to do a second version. We can iterate through multiple rounds to finally get good code, then merge it into the main development branch.
- Before starting a new development, first create a requirement and do various research to create a spec called
01-draft-spec.md. - Create a git worktree:
mkdir -p .worktree && git worktree add .worktree/spec-180-impl-01 -b spec-180-impl-01. - Add
.worktreeto.gitignore. - Change directory to
.worktree/spec-180-impl-01and start to implement. - After implementation, test and verify if it works as expected.
- If it does, change to the main branch (
git checkout main), mergespec-180-impl-01, and then remove the worktree:git worktree remove .worktree/spec-180-impl-01. - If it is not working well, do research again, update the spec, and then repeat the git worktree process.
- If it does, change to the main branch (
- We can even create multiple worktrees simultaneously and use different code agents to implement, only merging the one which is best.