frontend
Frontend Documentation
Note for Claude: Read this documentation when working in the
assets/ui/directory for frontend tasks.
Admin CRUD Pages
The admin interface follows a consistent CRUD (Create, Read, Update, Delete) pattern for managing entities.
Directory Structure
assets/ui/admin/[entity]/
├── catalog/ # List view of all entities
├── show/ # Detail view of single entity
├── update/ # Edit form for entity
├── create/ # Creation form (optional)
└── button/ # Action buttons
├── run/
├── delete/
└── refresh/
Catalog Page Pattern
List all entities in a table format with links to detail pages.
Key Features:
- Extends
$trip2g_admin_catalogwhich provides table layout - Uses
$trip2g_graphql_requestto fetch data - Converts data to map with
$trip2g_graphql_make_map - Links to show pages via
ShowPage*components
Structure (catalog.view.tree):
$trip2g_admin_[entity]_catalog $trip2g_admin_catalog
menu_title \[Entity Name]
ShowPage* $trip2g_admin_[entity]_show
[entity]_id <= row_id* 0
menu_link_content* / <= [Entity]Item* $mol_view
sub /
<= Rows* $mol_row
sub <= row_content* /
<= Row_id_labeler* $mol_labeler
title \ID
Content <= Row_id* $trip2g_admin_cell
Business Logic (catalog.view.ts):
namespace $.$$ {
export class $trip2g_admin_[entity]_catalog extends $.$trip2g_admin_[entity]_catalog {
@$mol_mem
data(reset?: null) {
const res = $trip2g_graphql_request(`query { admin { all[Entities] { nodes { id name } } } }`)
return $trip2g_graphql_make_map(res.admin.all[Entities].nodes)
}
row(id: any) { return this.data().get(id) }
override row_id(id: any): number { return this.row(id).id }
}
}
Show Page Pattern
Display detailed information about a single entity with edit/action buttons.
Structure (show.view.tree):
$trip2g_admin_[entity]_show $mol_page
[entity]_id? 0
tools /
<= EditLink $mol_link
arg * action \update
<= DeleteLink $mol_link
arg * action \delete
title \Delete
DeleteForm $trip2g_admin_[entity]_delete
[entity]_id <= [entity]_id
body /
<= Details $mol_view
sub /
<= Details_row $mol_view
style *
flexDirection \column
sub /
<= Id_labeler $mol_labeler
title \ID
Content <= Id $trip2g_admin_cell
content <= [entity]_id_string \
<= Name_labeler $mol_labeler
title \Name
Content <= Name $trip2g_admin_cell
content <= [entity]_name \
Vertical Layout: Use $mol_view with style * flexDirection \column for vertical stacking of labelers instead of $mol_row for better readability.
Business Logic (show.view.ts):
namespace $.$$ {
export class $trip2g_admin_[entity]_show extends $.$trip2g_admin_[entity]_show {
@$mol_mem
[entity]_data(reset?: null) {
const res = $trip2g_graphql_request(`query($id: Int64!) { admin { [entity](id: $id) { id name } } }`, { id: this.[entity]_id() })
return res.admin.[entity]
}
}
}
Update Form Pattern
Provide form interface for editing entity properties.
Structure (update.view.tree):
$trip2g_admin_[entity]_update $mol_view
[entity]_id 0
sub /
<= Form $mol_form
body /
<= Field_field $mol_form_field
Content <= field_control $mol_string
value? <=> field? \
buttons /
<= Submit $mol_button_major
click? <=> submit? null
<= Result $mol_status
message <= result? \
Action Button Pattern
IMPORTANT: All action buttons must be separated into their own components under button/[action]/.
Structure (button/[action]/[action].view.tree):
$trip2g_admin_[entity]_button_[action] $mol_button_major
[entity]_id 0
title <= status_title? \[Action]
click? <=> [action]? null
Logic (button/[action]/[action].view.ts):
namespace $.$$ {
export class $trip2g_admin_[entity]_button_[action] extends $.$trip2g_admin_[entity]_button_[action] {
[action](event?: Event) {
const res = $trip2g_graphql_request(`mutation($input: [Action][Entity]Input!) { admin { [action][Entity](input: $input) { ... on [Action][Entity]Payload { success } ... on ErrorPayload { message } } } }`, { input: { id: this.[entity]_id() } })
if(res.admin.[action][Entity].__typename === 'ErrorPayload') {
throw new Error(res.admin.[action][Entity].message)
}
this.status_title('[Action]: Success')
}
}
}
Button Disabled State
To disable a button based on condition, use the disabled property:
Structure (show.view.tree):
<= SetActiveButton $mol_button_minor
disabled <= is_active false
title <= set_active_title \Set Active
click? <=> set_active_click? null
Logic (show.view.ts):
is_active() {
return this.data().active
}
set_active_title() {
return this.data().active ? 'Active' : 'Set Active'
}
The button will be grayed out and unclickable when is_active() returns true.
CSS Styling
Column Width Guidelines:
- ID columns:
rem(3) - Dates/timestamps:
rem(8) - Names/titles:
rem(12) - Descriptions:
rem(15)
Example (catalog.view.css.ts):
namespace $.$$ {
const { rem } = $mol_style_unit
$mol_style_define($trip2g_admin_[entity]_catalog, {
Row_id_labeler: { flex: { basis: rem(3) } },
Row_name_labeler: { flex: { basis: rem(12) } },
})
}
Mol Framework Essentials
Source Code Reference
$mol framework sources are available at ../mam/mol/. When you need to understand how a $mol component works (e.g., $mol_form, $mol_list), read its .view.tree file:
cat ../mam/mol/form/form.view.tree
cat ../mam/mol/list/list.view.tree
View Definitions
- Tree-based UI specs in
.view.treefiles, behavior in.view.ts - List views use
$trip2g_graphql_request+$trip2g_graphql_make_map(), then definerow(id)methods
Routing & Linking
- Main admin uses
spreadsto switch pages bynavarg - Wire detail pages via
Content* $trip2g_admin_show_Xandparam \x_id <= row_id*
Detail/Edit Pages
- Show pages fetch single records via GraphQL query
- Use
$mol_labeler,$mol_date,$mol_time_momentfor form controls - Bind inputs two-way using
<=>, e.g.,value_moment? <=> expires_at_moment?
GraphQL Requests
- Use
$trip2g_graphql_requestfor all queries/mutations - Run
npm run graphqlgenafter modifying schema
Date/Time Formatting
$mol_time_moment
Date Input Controls:
- Use
$mol_datefor date inputs:value_moment? <=> date_moment? null
Time/DateTime Formatting for GraphQL:
- Use
moment.toString()for Go backend (ISO8601 format) - Date-only inputs: Convert to full datetime:
startsAt: this.starts_at_moment() ? new $mol_time_moment(this.starts_at_moment().toString() + 'T00:00:00Z').toString() : null - DateTime inputs:
createdAt: this.created_at_moment()?.toString() || null
Patterns:
toString()producesYYYY-MM-DDThh:mm:ss.sssZ(ISO8601)- Custom patterns:
toString('YYYY-MM-DD')for date-only - For display:
toString('DD.MM.YYYY hh:mm')
Localization
The project localization process can follow two different approaches: through project resource files or through online web server requests. $mol uses the first approach by default.
Localization in view.tree
Hardcoding texts in code is simple and fast, but what about localization? Simply add the @ symbol and the text after it will be extracted to a separate file with translations, while the generated TypeScript class from view.tree will only contain a call by a human-readable key.
Localization example:
$trip2g_admin_telegrampublishnote_catalog $trip2g_admin_catalog
menu_title @ \Telegram Publish Notes
ShowSent $mol_check_box
title <= show_sent_title @ \Show Sent ({count})
checked? <=> show_sent? false
Row_title_labeler* $mol_labeler
title @ \Title
Content <= Row_title* $trip2g_admin_cell
Localization Process
The localization process consists of several steps:
-
Add the
@sign before the component property value that needs to be translated into different languages. The translation key will be automatically generated from the source, and you can view it locally in thecomponent.view.tree.locale=en.jsonfile after running the project. -
Create a translation file in the component folder for each locale. The file will have the same name as the component, but with a suffix.
Example for Russian: component.view.tree.locale=ru.json, where ru is the language code. The EN locale is automatically extracted from the component.
Localization file structure:
{
"$trip2g_admin_telegrampublishnote_catalog_show_sent_title": "Show Sent ({count})",
"$trip2g_admin_telegrampublishnote_catalog_show_outdated_title": "Show Outdated ({count})",
"$trip2g_admin_telegrampublishnote_catalog_Row_title_labeler_title": "Title",
"$trip2g_admin_telegrampublishnote_catalog_Row_publish_at_labeler_title": "Publish At",
"$trip2g_admin_telegrampublishnote_catalog_Row_status_labeler_title": "Status",
"$trip2g_admin_telegrampublishnote_catalog_menu_title": "Telegram Posts"
}
$trip2g_admin_telegrampublishnote_catalog_show_sent_title is a key obtained according to the FQN component name ($trip2g_admin_telegrampublishnote_catalog) + property name (show_sent_title).
Browser Usage
-
Open the browser with the application and developer console (F12 or Ctrl+Shift+I).
-
Enter commands in the console to change the project locale:
$mol_locale.lang('en')- English language$mol_locale.lang('ru')- Russian language
This command will change all localized texts on the site. If you enter a locale that doesn't exist
$mol_locale.lang('it'), the default locale (en) will be applied. -
Current locale is stored in localStorage of the site, so when the browser restarts, the user will retain their selected language.
-
Get translations for all keys on the site for the selected locale using the command
$mol_locale.texts('ru'). This will include all texts, even those used in other components.
Localization Workflow
When asked to localize a component, follow these steps:
- Mark text fields for localization - Go into the component and mark fields like
titlewith@symbol - Check for existing Russian locale file - Look for
component.view.tree.locale=ru.jsonin the component folder - Copy keys from generated English file - After marking fields with
@, the system generatescomponent/-/component.view.tree.locale=en.json - Transfer keys and translate - Copy the keys from the English file to the Russian file and provide Russian translations
Example workflow for assets/ui/admin/telegrampublishnote/show/show.view.tree:
- Mark titles with
@:title @ \Post Content - Check if
assets/ui/admin/telegrampublishnote/show/show.view.tree.locale=ru.jsonexists - Copy keys from
assets/ui/admin/telegrampublishnote/show/-/show.view.tree.locale=en.json - Add Russian translations to the ru.json file
Key Points
- Automatic key generation: Localization keys are automatically generated based on component FQN name and property
- English fallback: If translation is not found, English text from the component is used
- Dynamic values: Texts can use placeholders like
{count}that are replaced at runtime - Locale hierarchy: The system automatically searches for translations from more specific to more general locales
Testing
- Libraries:
github.com/kr/pretty,github.com/matryer/moq,github.com/stretchr/testify/require - Pattern: Table-driven tests with mock setup functions
- Mocks: Generate with
//go:generate go tool github.com/matryer/moq -out mocks_test.go . Env