Redesign and Port Credentials to React
Nobody has claimed this yet.
- Dominant language
- Elixir
- Stars
- 296
- Forks
- 86
- Avg merge
- 1d 13h
- Merged PRs (30d)
- 50
Description
Why We Need This
Right now, managing credentials in Lightning feels inconsistent and unnecessarily complex. Users experience different flows depending on where they're working—the collaborative editor opens LiveView modals that disrupt real-time collaboration, while the dedicated credentials pages cram complex multi-step forms into constrained modal windows. We can do so much better.
This epic is about fundamentally rethinking how users create and manage credentials across Lightning. We want to give them the right tools in the right context: quick, seamless credential creation when they're deep in workflow building, and spacious, thoughtful interfaces when they're managing their credential library. The technical migration to React is important, but it's really just enabling better user experiences.
What We're Building
We're focusing on the core credential lifecycle: creating, editing, and deleting both regular credentials (including OAuth-based ones) and keychain credentials. We're not touching OAuth client management or the credential listing tables yet—those can come later once we've proven out the patterns here.
This isn't just a port of existing code. It's a chance to simplify and improve the user experience based on what we've learned from how people actually use Lightning.
The Problems We're Solving
The Code Has Grown Too Complex
Our current credential form implementation has sprawled across about 1,500 lines of LiveView code in just two files (credential_form_component.ex and generic_oauth_component.ex). It's doing a lot: managing multi-page navigation, juggling async OAuth token fetching, handling up to five different environments per credential, validating OAuth scopes, refreshing expired tokens, tracking which workflows use which credentials... the list goes on. This complexity makes it hard to maintain, harder to extend, and honestly, it shows in the user experience.
The User Experience is Inconsistent
Here's what really matters: our users are experiencing wildly different flows depending on where they're creating credentials. If you're building a workflow in the collaborative editor, clicking "Add Credential" suddenly opens a LiveView modal that interrupts your flow and breaks real-time collaboration. If you're on the dedicated /credentials page, you get the same cramped modal experience even though you have the entire screen available. It doesn't make sense.
Think about the context: When someone is deep in workflow building with their team, they want quick, inline credential creation that doesn't disrupt their collaborative session. But when they're deliberately managing their credential library, they probably want space to breathe—room to carefully configure OAuth settings, set up multiple environments, and see what they're doing.
Specific Issues We've Identified
The Modal Problem: Right now, whether you're on /credentials or /projects/:id/settings, you're creating credentials in modals. But why? When you have an entire page dedicated to managing credentials, why constrain the UI to a small modal window? This is especially painful for OAuth credentials where users need to understand scope selection, handle authorization flows, and potentially configure multiple environments. Modals make sense in the workflow editor where you want to maintain context, but on management pages they're just getting in the way.
OAuth Flows Are Tricky: Authorizing OAuth credentials involves opening new windows, waiting for redirects, fetching tokens asynchronously, and sometimes retrieving user info from external APIs. All of this happens while the user might be clicking around or even closing the modal. We need a more resilient, clear flow that helps users understand what's happening and what to expect.
Environment Management Feels Heavy: We support up to five environments per credential (production, staging, etc.), which is powerful but the current tab-based interface with inline editing and sorting feels complex. Do users actually need all this in the default flow? Should it be progressive disclosure instead?
The React ↔ LiveView Dance: Having React components trigger LiveView modals that then push updates back to React via Phoenix channels creates synchronization challenges, performance overhead, and a janky feel when you're collaborating with others in real-time.
What Success Looks Like
We'll know we've succeeded when:
-
Creating credentials feels natural in context: Quick and unobtrusive when you're building workflows, spacious and clear when you're managing your credential library.
-
Collaboration isn't disrupted: When multiple people are editing a workflow together, adding or updating credentials feels like a seamless part of the collaborative experience.
-
OAuth flows are clear and forgiving: Users understand what's happening when they authorize credentials, and the system gracefully handles edge cases like closed windows or slow API responses.
-
The code is maintainable: Future developers can understand and extend the credential flows without getting lost in complexity.
-
We've learned from the redesign: The patterns we establish here should inform how we approach other complex forms and features in Lightning.
The Big Questions We Need to Answer Together
Before we start building, we need to make some important decisions with the product team. These aren't just technical choices—they're about understanding how people actually work with credentials and designing flows that support them. We'll need Taylor (@taylordowns2000) and Brandon's input, along with anyone who has insights into how our users manage credentials in practice.
Should We Use Modals or Full Pages on Management Screens?
This is probably the most important UX decision we need to make. Right now, both the /credentials page and the project settings use modals for everything. But think about it: when you're on a page whose entire purpose is managing credentials, why squeeze the form into a modal?
Here are the options we're considering:
Option A: Keep modals everywhere
This would be consistent—you'd always create credentials the same way. But it also means you're always constrained by that modal window, even when you're on a dedicated management page with tons of screen real estate available. Complex OAuth flows with scope selection and multiple environments feel pretty cramped in a modal.
Option B: Full pages for management, modals in the workflow editor
This acknowledges that the workflow editor and management pages serve different purposes. In the workflow editor, you want a modal because you're in the middle of building something and don't want to lose context. But on management pages, you could navigate to /credentials/new and have all the space you need to thoughtfully configure a credential.
Option C: Hybrid based on complexity
Simple credentials get modals, OAuth credentials get full pages. This optimizes for each use case but might feel inconsistent—"wait, why am I on a new page now?"
Our initial thinking is that Option B makes the most sense. The workflow editor context really does need modals to avoid disrupting collaboration and losing where you were in the workflow. But management pages could benefit from the breathing room. That said, we need input from people who actually watch users work with credentials. What do they expect? What would feel most natural?
How Different Should the Workflow Editor Experience Be?
Here's a common scenario: Someone is building a workflow and realizes halfway through configuring a job that they need to create a new credential. They're in flow state, probably collaborating with teammates, and they just want to quickly add the credential and get back to what they were doing.
Compare that to someone who's deliberately going to the credentials page to set up their organization's credentials properly—maybe configuring OAuth with specific scopes, setting up multiple environments for production and staging, adding external IDs for tracking. They're in a different mindset and have different needs.
Questions we need to explore:
-
Should the workflow editor offer a simplified "quick create" flow? Maybe just the essentials: name, type, and basic configuration. Then users could flesh out the details later from the management pages if needed.
-
Or should both contexts offer the same complete form, just presented differently (modal vs full page)?
-
What about environment configuration? Right now, creating from the workflow editor defaults to a "main" environment. Is that the right default? Should users be able to specify the environment in-context, or should advanced configuration always happen on management pages?
-
If we do have different flows, how do we make it clear to users that they can go enhance their credentials with more settings later?
Do We Really Need Multiple Environments Up Front?
Currently, you can configure up to five environments per credential (production, staging, development, etc.), each with its own configuration. It's a powerful feature, but the tab-based interface adds a lot of complexity right from the start.
Here's what we're wondering:
-
Do most users actually use multiple environments? We should look at the data. If most credentials only ever have one environment, maybe we're optimizing for the wrong case.
-
What if we started simple—just one environment configuration—with a clear "Add another environment" option that progressively reveals the multi-environment interface for those who need it?
-
Is the tab interface the right pattern anyway? We could explore accordions, or even separate pages for environment management if we go with the full-page approach.
-
Should environment management be a separate step in the creation flow, or even something you can only do after the initial credential is created?
How Should OAuth Authorization Actually Feel?
OAuth credentials are tricky because they involve async operations that can take several seconds: opening new windows for authorization, exchanging codes for tokens, fetching user information from external APIs. Currently all of this happens within a modal that users might close or navigate away from.
We need to figure out:
-
How do we communicate what's happening during these long-running operations? Users should never wonder if something froze or if they should click again.
-
Should users be able to continue working in Lightning while OAuth authorization completes in the background? Or is that more confusing than helpful?
-
What happens if someone closes the modal mid-authorization? Should we save their progress? Show a warning?
-
Would it make sense for OAuth credentials to have their own dedicated flow—maybe even a step-by-step wizard—separate from simple credential creation?
What About Advanced Features?
We have some features that are powerful but probably rarely used: transferring credential ownership to another user, scheduling credentials for deletion, adding external IDs for integration tracking. Right now these features are all visible in the main form.
Questions to consider:
-
Should these live behind an "Advanced" section that most users never need to see?
-
Could credential transfer be its own separate workflow, accessed from the credential list, rather than embedded in the edit form?
-
What does usage data tell us about which features are actually essential vs nice-to-have?
Should We Simplify or Port Everything As-Is?
This is really the meta-question: Are we just moving existing code to React, or are we redesigning the experience?
Option A: Port the current complexity
We maintain complete feature parity, which is safe, but we also keep all the complexity that we know is causing problems.
Option B: Redesign with simplification in mind
We use this as an opportunity to simplify and improve, starting with the most common use cases and progressively disclosing advanced features. This requires design work and careful thought about what's truly essential.
Option C: Ship a simple v1, then iterate
We could launch quickly with a simplified version and add complexity back as we learn what users actually need. This is faster but might mean rework.
Our instinct is Option B—this is a rare chance to rethink an important flow based on what we've learned. But we need product team buy-in on taking the time to do it thoughtfully.
What We Need Before Building
To do this right, we need to start with design and user research, not code. Here's what would be helpful:
User flow diagrams that map out the different paths: creating credentials from the workflow editor vs the management pages, handling OAuth authorization, managing multiple environments—all the key journeys we've identified.
Wireframes or mockups that explore different approaches to the questions above. We don't need pixel-perfect designs, but visual explorations of how these flows could work would help us make informed decisions.
Clear decisions on the big questions: Modal vs full page? Simplified vs complete forms in different contexts? Progressive disclosure for advanced features? These aren't engineering decisions—they're product decisions that should be grounded in how users actually work.
Feature prioritization: What's truly essential for an initial release? What could come later? If we're going to simplify, we need to know what can safely be deprioritized.
Usage data review: It would be really valuable to understand how people currently use credentials. How many create OAuth vs simple credentials? How many use multiple environments? What errors or frustrations do they encounter?
Scope Details
What We're Porting
CRUD Operations Only: Create, Update, Delete forms/modals for:
- Regular credentials (JSON schema-based, raw JSON, OAuth using existing clients)
- Keychain credentials
What Stays in LiveView (For Now)
Credential Listing Tables: The data tables showing lists of credentials will remain in LiveView on:
/credentialspage/projects/:id/settings#credentialstab
Rationale: Tables are simpler, work well in LiveView, and aren't blocking the main UX improvements. Can be migrated later if needed.
OAuth Client CRUD: All OAuth client management remains in LiveView (separate epic).
Integration Points
1. Workflow Editor (Primary Focus)
Current: Opens LiveView modals from React
Target: Native React modals/forms with full Y.Doc integration
User Flow:
- User configures job in React workflow editor
- Clicks "Add Credential" or "Edit Credential"
- React modal opens (no LiveView jump)
- User completes form, saves
- Credential immediately available in Y.Doc
- Other collaborators see update in real-time
2. Management Pages (/credentials, /settings)
Current: LiveView tables with LiveView modals for CRUD
Target (pending design decision):
- Option A: LiveView tables with React modals for CRUD
- Option B: LiveView tables with React full-page forms for CRUD
User Flow (Option B example):
- User views credential list (LiveView table)
- Clicks "Create Credential" or "Edit"
- Navigates to
/credentials/newor/credentials/:id/edit(React full page) - Completes form, saves
- Redirects back to list
- LiveView table refreshes to show changes
Note: This requires product design decision (see Design Questions section).
Modal Components to Port
Credential Form Modal
Current: credential_form_component.ex (~1,000 lines)
Features:
- Two-page flow:
- Schema picker: Visual grid of adaptor icons + "Raw JSON" + OAuth clients (OAuth selection handled by existing schema picker)
- Configuration form: Multi-environment credential setup
- Multi-environment tabs (up to 5: production, staging, etc.)
- Dynamic environment naming with validation (pattern:
^[a-z0-9][a-z0-9_-]{0,31}$) - Schema-based forms (JSON Schema → form fields)
- OAuth credential flow (using existing OAuth clients):
- OAuth client selection dropdown
- Scope selection
- Authorization button
- Token fetching
- Async userinfo retrieval
- Project access picker (multi-select with chips)
- Credential transfer section (edit mode only)
- Environment-specific credential bodies
- Validation and error handling
Required sub-components:
- SchemaPickerPage (with OAuth client option)
- CredentialConfigurationPage
- EnvironmentTabs
- EnvironmentEditor
- JsonSchemaBodyForm
- RawJsonBodyForm
- OAuthAuthorizationFlow (uses existing OAuth clients)
- ProjectAccessPicker
- CredentialTransferForm
Keychain Credential Form Modal
Current: keychain_credential_form_component.ex
Features:
- Name input (with validation)
- JSONPath expression editor for dynamic credential selection
- Default credential dropdown
- Project scope
Supporting Modals
- Transfer Credential Modal: Email input with validation, revoke transfer button
- Delete Credential Modal: Confirmation with warnings about dependent workflows
- Delete Keychain Credential Modal: Confirmation for keychain deletion
Data Tables to Port
Credentials Table
Columns: Name, Type, Owner (optional), Projects, External ID, Environments, Actions
Features:
- Sortable columns
- Action dropdown per row
- Owner display (project settings only)
- Environment badges
- Project count badges
- Empty state
Keychain Credentials Table
Columns: Name, Path, Default Credential, Actions
Features:
- JSONPath display
- Default credential name
- Action dropdown
- Empty state
OAuth Clients Table (Read-Only in React)
Columns: Name, Owner (optional), Projects, Authorization URL, Actions
Features:
- Display OAuth clients for credential creation
- Action dropdown opens LiveView modals
- "Create OAuth Client" button opens LiveView modal
- Authorization URL display
- Project count badges
- Empty state
Implementation Note: This table renders in React but all CRUD actions delegate to LiveView modals.
Technical Requirements
Store Architecture
Create new Zustand stores following collaborative editor patterns:
CredentialManagementStore
{
// State
credentials: ProjectCredential[],
keychainCredentials: KeychainCredential[],
oauthClients: OauthClient[], // read-only, for display and credential creation
selectedCredential: ProjectCredential | null,
activeModal: 'create' | 'edit' | 'delete' | 'transfer' | null,
// Queries
findCredentialById(id: string): ProjectCredential | undefined,
getProjectCredentials(projectId: string): ProjectCredential[],
getUserCredentials(): ProjectCredential[],
getOauthClients(): OauthClient[], // read-only
// Commands
openCreateModal(type: 'credential' | 'keychain'): void,
openEditModal(credential: ProjectCredential): void,
openDeleteModal(credential: ProjectCredential): void,
openTransferModal(credential: ProjectCredential): void,
closeModal(): void,
createCredential(data: CredentialFormData): Promise<void>,
updateCredential(id: string, data: CredentialFormData): Promise<void>,
deleteCredential(id: string): Promise<void>,
transferCredential(id: string, email: string): Promise<void>,
// OAuth Client delegation (opens LiveView modals)
openOauthClientModal(action: 'create' | 'edit' | 'delete', clientId?: string): void,
}
CredentialFormStore (ephemeral, modal-scoped)
{
// State
currentPage: 1 | 2,
selectedSchema: string | null, // includes OAuth client IDs
selectedOauthClient: OauthClient | null,
environments: EnvironmentTab[],
currentTab: string,
environmentBodies: Record<string, CredentialBody>,
selectedProjects: string[],
// Commands
selectSchema(schemaId: string): void,
selectOauthClient(clientId: string): void, // for OAuth credentials
addEnvironment(name: string): void,
removeEnvironment(name: string): void,
updateEnvironmentName(oldName: string, newName: string): void,
switchTab(tabName: string): void,
updateBody(environment: string, body: CredentialBody): void,
toggleProject(projectId: string): void,
reset(): void,
}
Phoenix Channel Integration
Extend existing credential channel or create new events:
Client → Server:
request_credentials- Fetch credentials list (including OAuth clients for display)create_credential- Create new credentialupdate_credential- Update existing credentialdelete_credential- Delete credentialtransfer_credential- Initiate transferoauth_authorize- Start OAuth flow (using existing OAuth client)oauth_refresh_token- Refresh OAuth token
Server → Client:
credentials_list- Initial/updated credentials list (includes OAuth clients)credential_created- New credential notificationcredential_updated- Update notificationcredential_deleted- Deletion notificationoauth_client_updated- OAuth client changed (from LiveView, refresh list)oauth_authorization_complete- OAuth flow completeoauth_token_refreshed- Token refresh completeerror- Operation error
Form Validation
Port Ecto validations to TypeScript with Zod schemas:
const CredentialSchema = z.object({
name: z.string().min(1).max(255),
schema: z.string().min(1), // includes "oauth:<client_id>" for OAuth credentials
oauth_client_id: z.string().uuid().optional(), // for OAuth credentials
external_id: z.string().optional(),
credential_bodies: z.array(z.object({
name: z.string().regex(/^[a-z0-9][a-z0-9_-]{0,31}$/),
body: z.record(z.unknown())
})).min(1).max(5),
project_ids: z.array(z.string().uuid())
});
const KeychainCredentialSchema = z.object({
name: z.string().min(1).max(255),
path: z.string().min(1), // JSONPath expression
default_credential_id: z.string().uuid().optional(),
project_id: z.string().uuid()
});
OAuth Flow Handling
Complex async flow requiring careful state management (using existing OAuth clients):
- User selects OAuth client from schema picker
- System loads OAuth client configuration
- User configures scopes (based on OAuth client's mandatory/optional scopes)
- User clicks "Authorize"
- Opens OAuth provider authorization URL in new window/tab (URL from OAuth client)
- After authorization, callback hits Lightning server
- Server exchanges code for tokens
- Server fetches userinfo (async)
- Server pushes tokens + userinfo to React via channel
- React updates credential body with token data
- User completes credential creation
Challenges:
- Async token fetch (may take seconds)
- Window/tab management for OAuth redirect
- Error handling (temporary failures vs reauthorization required)
- Token refresh logic (5-minute expiry buffer)
- Preserving refresh tokens on update
- OAuth client configuration comes from LiveView-managed OAuth clients
Environment Management
Complex tab-based UI with constraints:
Rules:
- Min 1 environment, max 5 environments
- Environment name pattern:
^[a-z0-9][a-z0-9_-]{0,31}$ - Unique names per credential
- Auto-sorting (primary names first: main, production, prod, master, default)
- Each environment has separate credential body
- Deletion tracking for updates (original vs current)
UI Requirements:
- Tab interface with active state
- Add environment button (disabled at 5)
- Inline name editor with validation
- Delete button per tab (disabled at 1)
- Visual validation feedback
- Body form switches based on active tab
Project Access Control
Multi-select component with usage warnings:
Features:
- Available projects list (not yet selected)
- Selected projects list (with badges)
- Add/remove projects
- Show workflows using credential (on remove)
- Warning if removing from project with active workflows
- Permission checks (can user access these projects?)
Transfer Workflow
Multi-step process:
- Owner initiates transfer (email input)
- System validates:
- Recipient exists
- Recipient has access to all projects using credential
- System generates transfer token
- System sends email to recipient
- Recipient clicks link (contains token)
- System confirms transfer
- System updates credential owner
- System sends confirmation emails
Edge cases:
- Revoke transfer before acceptance
- Expired transfer tokens
- Recipient lacks project access
- Credential deleted before acceptance
Permission System
All operations require permission checks:
Permissions to enforce:
:credentials, :create_keychain_credential- Create keychain:credentials, :edit_keychain_credential- Edit keychain:credentials, :delete_keychain_credential- Delete keychain:users, :edit_credential- Edit credential:users, :delete_credential- Delete credential:project_users, :create_project_credential- Create project credential
Implementation: Phoenix Channel validates permissions before operation
LiveView Interop for OAuth Clients
React components need to delegate OAuth client management to LiveView:
Pattern:
// Open LiveView modal from React
function openOauthClientModal(action: 'create' | 'edit' | 'delete', clientId?: string) {
window.liveSocket.channel.push('open_oauth_client_modal', {
action,
client_id: clientId
});
}
// Listen for OAuth client changes
channel.on('oauth_client_updated', () => {
// Refresh OAuth clients list
requestCredentials();
});
LiveView responsibilities:
- OAuth client CRUD modals
- OAuth client validation
- Pushing updates to React via channel
Routing Changes
Keep LiveView routes for OAuth clients:
- OAuth client modal actions (opened from React)
Remove LiveView routes for credentials:
- Credential-specific modal routes
Add React routes:
GET /credentials→ React app mount point- Settings tab mounts React component instead
Keep API routes:
GET /api/credentialsPOST /api/credentialsPUT /api/credentials/:idDELETE /api/credentials/:id- (Extend as needed for new operations)
Testing Strategy
Unit Tests (Vitest)
- Store logic (commands, queries)
- Form validation (Zod schemas)
- Component rendering
- User interactions
- Permission checks
- LiveView modal delegation
Integration Tests (Vitest)
- Store + Channel integration
- Multi-step form flows
- Environment management
- OAuth flow (mocked, using existing OAuth clients)
- React ↔ LiveView OAuth client interop
E2E Tests (Playwright)
- Full credential creation flows
- OAuth authorization (mocked provider, existing OAuth clients)
- Transfer workflow
- Delete with warnings
- Collaborative editing scenarios
- Permission enforcement
- OAuth client modal integration from React
Success Criteria
- All credential and keychain credential features working in React
- OAuth client management remains in LiveView
- Seamless interop between React and LiveView for OAuth clients
- No LiveView credential/keychain components remain
- Feature parity with current implementation
- All tests passing (>80% coverage)
- Accessibility compliant (WCAG 2.1 AA)
- Real-time collaboration working
- Documentation complete
- Performance meets benchmarks
Related Issues
- OAuth token refresh logic improvements
- Credential environment selection in sandboxes
- Credential usage tracking
Other Questions to Explore
As we dig into this, we'll probably uncover more questions, but here are a few we're already thinking about:
Collaborative editing: Should multiple users be able to edit the same credential simultaneously? Or is that more complexity than we need? Credentials aren't edited as frequently as workflows, so maybe it's okay to have simple conflict detection ("Someone else edited this credential, please refresh") rather than real-time collaboration.
Migration strategy: How do we roll this out without disrupting existing users? Should we have a feature flag period where both systems exist? What happens to credentials being edited when we deploy?
OAuth client interactions: Since OAuth clients are staying in LiveView for now, how do we handle updates to OAuth clients while someone has a React credential form open? Do we need to refresh the available clients list?
Schema loading: Should credential schemas (the JSON schemas that define what fields different credential types need) be fetched dynamically from the server, or bundled with the React app? Dynamic is more flexible but adds network requests.
How We'll Move Forward
This issue is really about starting a conversation and documenting what we know so far. Before anyone writes React code, we need to:
-
Get the product team together—Taylor (@taylordowns2000), Brandon, and anyone else who should be involved—to discuss the design questions raised here.
-
Create design artifacts that explore the options we've discussed. These don't need to be final, just enough to help us make informed decisions.
-
Decide on an approach together—not just what patterns to use, but what success looks like and what we're prioritizing.
-
Break this down into smaller pieces once we have clarity. This issue is intentionally high-level. Once we know what we're building, we can create specific tickets for implementation.
The goal isn't to have all the answers now. It's to identify the questions that need answering and make sure we're involving the right people in those decisions.
Contributor guide
No contributing guide indexed for this repository
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Assessment
This issue has not been assessed yet.