comptext-codex 5.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,497 @@
1
+ """Module K: Frontend/UI - Component scaffolding, accessibility, responsive design."""
2
+
3
+ from typing import Any, Dict, List
4
+
5
+ from comptext_codex.registry import codex_module, codex_command
6
+ from .base import BaseModule
7
+
8
+
9
+ @codex_module(
10
+ code="K",
11
+ name="Frontend/UI",
12
+ purpose="Component scaffolding, accessibility, and responsive design",
13
+ token_priority="medium",
14
+ security={"pii_safe": True, "threat_model": "xss_prevention"},
15
+ privacy={"dp_budget": "epsilon<=1.0_per_call"},
16
+ )
17
+ class ModuleK(BaseModule):
18
+ """Frontend/UI module for component generation."""
19
+
20
+ @codex_command(syntax="@COMPONENT[framework, type, ...]", description="Generate UI component with configuration", token_cost_hint=65)
21
+ def execute_component(self, *args, context: Dict[str, Any] = None, **kwargs) -> Dict[str, Any]:
22
+ """Generate UI component with comprehensive configuration."""
23
+ framework = kwargs.get('framework', 'react')
24
+ comp_type = kwargs.get('type', 'functional')
25
+ styling = kwargs.get('styling', 'css-modules')
26
+ typescript = kwargs.get('typescript', True)
27
+ state = kwargs.get('state', 'local')
28
+
29
+ extension = 'tsx' if typescript else 'jsx'
30
+ test_framework = kwargs.get('test_framework', 'jest')
31
+
32
+ files = [
33
+ f'Component.{extension}',
34
+ f'Component.test.{extension}',
35
+ f'Component.module.{styling}' if 'modules' in styling else f'Component.{styling}',
36
+ ]
37
+
38
+ if state in ['redux', 'mobx', 'zustand']:
39
+ files.append(f'Component.store.{extension}')
40
+
41
+ if kwargs.get('storybook', False):
42
+ files.append(f'Component.stories.{extension}')
43
+
44
+ return {
45
+ 'framework': framework,
46
+ 'type': comp_type,
47
+ 'styling': styling,
48
+ 'typescript': typescript,
49
+ 'state_management': state,
50
+ 'files_generated': files,
51
+ 'accessibility': kwargs.get('accessibility', 'wcag_aa'),
52
+ 'responsive': kwargs.get('responsive', True),
53
+ 'props': self._generate_prop_types(kwargs.get('props', [])),
54
+ 'hooks': self._recommend_hooks(comp_type, state),
55
+ 'dependencies': self._get_dependencies(framework, styling, state)
56
+ }
57
+
58
+ @codex_command(syntax="@DASHBOARD[layout, theme, ...]", description="Generate comprehensive dashboard layout", token_cost_hint=70)
59
+ def execute_dashboard(self, *args, context: Dict[str, Any] = None, **kwargs) -> Dict[str, Any]:
60
+ layout = kwargs.get('layout', 'grid')
61
+ theme = kwargs.get('theme', 'light')
62
+ responsive = kwargs.get('responsive', True)
63
+
64
+ components = {
65
+ 'navigation': {
66
+ 'type': kwargs.get('nav_type', 'sidebar'),
67
+ 'items': kwargs.get('nav_items', ['Dashboard', 'Analytics', 'Settings'])
68
+ },
69
+ 'header': {
70
+ 'include_search': kwargs.get('search', True),
71
+ 'user_menu': kwargs.get('user_menu', True),
72
+ 'notifications': kwargs.get('notifications', True)
73
+ },
74
+ 'main': {
75
+ 'layout': layout,
76
+ 'columns': kwargs.get('columns', 12),
77
+ 'gap': kwargs.get('gap', 16)
78
+ },
79
+ 'widgets': self._generate_widget_config(kwargs)
80
+ }
81
+
82
+ return {
83
+ 'layout': layout,
84
+ 'theme': theme,
85
+ 'responsive': responsive,
86
+ 'components': components,
87
+ 'charts': kwargs.get('charts', ['line', 'bar', 'pie', 'area']),
88
+ 'data_refresh': kwargs.get('refresh_interval', 30),
89
+ 'export_formats': ['pdf', 'csv', 'excel'],
90
+ 'filters': self._generate_filter_config(kwargs),
91
+ 'real_time': kwargs.get('real_time', False),
92
+ 'mobile_optimized': responsive
93
+ }
94
+
95
+ @codex_command(syntax="@FORM[fields, validation, ...]", description="Generate form with validation and accessibility", token_cost_hint=60)
96
+ def execute_form(self, *args, context: Dict[str, Any] = None, **kwargs) -> Dict[str, Any]:
97
+ fields = kwargs.get('fields', [])
98
+ validation = kwargs.get('validation', 'yup')
99
+
100
+ field_configs = []
101
+ for field in fields:
102
+ field_configs.append({
103
+ 'name': field,
104
+ 'type': self._infer_field_type(field),
105
+ 'validation': self._generate_validation_rules(field),
106
+ 'accessibility': {
107
+ 'label': field.replace('_', ' ').title(),
108
+ 'aria-label': f'Enter {field}',
109
+ 'required': True
110
+ }
111
+ })
112
+
113
+ return {
114
+ 'fields': field_configs,
115
+ 'validation_library': validation,
116
+ 'submit_handler': 'async',
117
+ 'error_handling': 'field-level',
118
+ 'accessibility_features': [
119
+ 'keyboard_navigation',
120
+ 'screen_reader_support',
121
+ 'error_announcements',
122
+ 'focus_management'
123
+ ],
124
+ 'features': {
125
+ 'auto_save': kwargs.get('auto_save', False),
126
+ 'multi_step': kwargs.get('multi_step', False),
127
+ 'file_upload': 'drag_drop' if kwargs.get('file_upload') else None
128
+ }
129
+ }
130
+
131
+ @codex_command(syntax="@LAYOUT[type, responsive, ...]", description="Generate responsive layout structure", token_cost_hint=50)
132
+ def execute_layout(self, *args, context: Dict[str, Any] = None, **kwargs) -> Dict[str, Any]:
133
+ layout_type = kwargs.get('type', 'flex')
134
+ responsive = kwargs.get('responsive', True)
135
+
136
+ layouts = {
137
+ 'flex': self._generate_flex_layout(kwargs),
138
+ 'grid': self._generate_grid_layout(kwargs),
139
+ 'masonry': self._generate_masonry_layout(kwargs),
140
+ 'split': self._generate_split_layout(kwargs)
141
+ }
142
+
143
+ layout_config = layouts.get(layout_type, layouts['flex'])
144
+
145
+ if responsive:
146
+ layout_config['breakpoints'] = {
147
+ 'mobile': '320px',
148
+ 'tablet': '768px',
149
+ 'desktop': '1024px',
150
+ 'wide': '1440px'
151
+ }
152
+
153
+ return layout_config
154
+
155
+ @codex_command(syntax="@THEME[colors, typography, ...]", description="Generate comprehensive theme configuration", token_cost_hint=55)
156
+ def execute_theme(self, *args, context: Dict[str, Any] = None, **kwargs) -> Dict[str, Any]:
157
+ base_color = kwargs.get('primary_color', '#3b82f6')
158
+
159
+ return {
160
+ 'colors': {
161
+ 'primary': base_color,
162
+ 'secondary': kwargs.get('secondary_color', '#8b5cf6'),
163
+ 'accent': kwargs.get('accent_color', '#06b6d4'),
164
+ 'success': '#10b981',
165
+ 'warning': '#f59e0b',
166
+ 'error': '#ef4444',
167
+ 'neutral': self._generate_neutral_scale()
168
+ },
169
+ 'typography': {
170
+ 'font_family': kwargs.get('font_family', 'Inter, system-ui, sans-serif'),
171
+ 'scale': self._generate_type_scale(),
172
+ 'weights': {'light': 300, 'regular': 400, 'medium': 500, 'semibold': 600, 'bold': 700}
173
+ },
174
+ 'spacing': self._generate_spacing_scale(),
175
+ 'shadows': self._generate_shadow_scale(),
176
+ 'borders': {
177
+ 'radius': {'sm': '4px', 'md': '8px', 'lg': '12px', 'xl': '16px', 'full': '9999px'},
178
+ 'width': {'thin': '1px', 'medium': '2px', 'thick': '4px'}
179
+ },
180
+ 'animations': {
181
+ 'duration': {'fast': '150ms', 'normal': '300ms', 'slow': '500ms'},
182
+ 'easing': {
183
+ 'ease_in': 'cubic-bezier(0.4, 0, 1, 1)',
184
+ 'ease_out': 'cubic-bezier(0, 0, 0.2, 1)',
185
+ 'ease_in_out': 'cubic-bezier(0.4, 0, 0.2, 1)'
186
+ }
187
+ },
188
+ 'dark_mode': kwargs.get('dark_mode', True),
189
+ 'css_variables': True
190
+ }
191
+
192
+ @codex_command(syntax="@ANIMATION[type, duration, ...]", description="Generate animation configuration", token_cost_hint=40)
193
+ def execute_animation(self, *args, context: Dict[str, Any] = None, **kwargs) -> Dict[str, Any]:
194
+ anim_type = kwargs.get('type', 'fade')
195
+ duration = kwargs.get('duration', 300)
196
+
197
+ animations = {
198
+ 'fade': {'opacity': [0, 1]},
199
+ 'slide': {'transform': ['translateY(20px)', 'translateY(0)']},
200
+ 'scale': {'transform': ['scale(0.9)', 'scale(1)']},
201
+ 'bounce': {'transform': ['scale(0.8)', 'scale(1.1)', 'scale(1)']},
202
+ 'rotate': {'transform': ['rotate(0deg)', 'rotate(360deg)']}
203
+ }
204
+
205
+ return {
206
+ 'type': anim_type,
207
+ 'keyframes': animations.get(anim_type, animations['fade']),
208
+ 'duration': duration,
209
+ 'timing_function': kwargs.get('easing', 'ease-in-out'),
210
+ 'delay': kwargs.get('delay', 0),
211
+ 'iteration_count': kwargs.get('iterations', 1),
212
+ 'direction': kwargs.get('direction', 'normal'),
213
+ 'fill_mode': 'both'
214
+ }
215
+
216
+ @codex_command(syntax="@RESPONSIVE[breakpoints, ...]", description="Generate responsive design configuration", token_cost_hint=45)
217
+ def execute_responsive(self, *args, context: Dict[str, Any] = None, **kwargs) -> Dict[str, Any]:
218
+ breakpoints = kwargs.get('breakpoints', {
219
+ 'xs': '320px',
220
+ 'sm': '640px',
221
+ 'md': '768px',
222
+ 'lg': '1024px',
223
+ 'xl': '1280px',
224
+ '2xl': '1536px'
225
+ })
226
+
227
+ return {
228
+ 'breakpoints': breakpoints,
229
+ 'container_max_widths': {
230
+ 'sm': '640px',
231
+ 'md': '768px',
232
+ 'lg': '1024px',
233
+ 'xl': '1280px'
234
+ },
235
+ 'fluid_typography': self._generate_fluid_typography(),
236
+ 'responsive_images': {
237
+ 'srcset': True,
238
+ 'lazy_loading': True,
239
+ 'webp_support': True
240
+ },
241
+ 'mobile_first': kwargs.get('mobile_first', True),
242
+ 'touch_optimized': {
243
+ 'min_tap_target': '44px',
244
+ 'swipe_gestures': kwargs.get('gestures', True)
245
+ }
246
+ }
247
+
248
+ @codex_command(syntax="@ACCESSIBILITY[level, features, ...]", description="Generate accessibility configuration", token_cost_hint=50)
249
+ def execute_accessibility(self, *args, context: Dict[str, Any] = None, **kwargs) -> Dict[str, Any]:
250
+ level = kwargs.get('level', 'wcag_aa')
251
+
252
+ return {
253
+ 'level': level,
254
+ 'features': {
255
+ 'semantic_html': True,
256
+ 'aria_labels': True,
257
+ 'keyboard_navigation': True,
258
+ 'focus_indicators': True,
259
+ 'skip_links': True,
260
+ 'screen_reader_support': True
261
+ },
262
+ 'color_contrast': {
263
+ 'minimum_ratio': 4.5 if level == 'wcag_aa' else 7,
264
+ 'large_text_ratio': 3 if level == 'wcag_aa' else 4.5
265
+ },
266
+ 'interactive_elements': {
267
+ 'min_size': '44x44px',
268
+ 'spacing': '8px',
269
+ 'clear_focus_state': True
270
+ },
271
+ 'forms': {
272
+ 'labels_for_inputs': True,
273
+ 'error_identification': True,
274
+ 'instructions_provided': True
275
+ },
276
+ 'media': {
277
+ 'alt_text': True,
278
+ 'captions': kwargs.get('captions', True),
279
+ 'audio_descriptions': kwargs.get('audio_desc', False)
280
+ },
281
+ 'testing': {
282
+ 'automated_tools': ['axe', 'lighthouse'],
283
+ 'manual_checks': True
284
+ }
285
+ }
286
+
287
+ # Helper methods
288
+
289
+ def _generate_prop_types(self, props: List[str]) -> Dict[str, str]:
290
+ """Generate prop type definitions."""
291
+ return {prop: self._infer_prop_type(prop) for prop in props}
292
+
293
+ def _infer_prop_type(self, prop: str) -> str:
294
+ """Infer prop type from name."""
295
+ if 'count' in prop or 'id' in prop or 'index' in prop:
296
+ return 'number'
297
+ elif 'is' in prop or 'has' in prop or 'show' in prop:
298
+ return 'boolean'
299
+ elif 'callback' in prop or 'handler' in prop or prop.startswith('on'):
300
+ return 'function'
301
+ elif 'list' in prop or 'items' in prop:
302
+ return 'array'
303
+ elif 'data' in prop or 'config' in prop:
304
+ return 'object'
305
+ return 'string'
306
+
307
+ def _recommend_hooks(self, comp_type: str, state: str) -> List[str]:
308
+ """Recommend React hooks based on component type."""
309
+ hooks = ['useState', 'useEffect']
310
+
311
+ if state == 'redux':
312
+ hooks.extend(['useSelector', 'useDispatch'])
313
+ elif state == 'context':
314
+ hooks.append('useContext')
315
+
316
+ if comp_type == 'performance':
317
+ hooks.extend(['useMemo', 'useCallback'])
318
+
319
+ return hooks
320
+
321
+ def _get_dependencies(self, framework: str, styling: str, state: str) -> List[str]:
322
+ """Get required dependencies."""
323
+ deps = [framework]
324
+
325
+ if styling == 'styled-components':
326
+ deps.append('styled-components')
327
+ elif styling == 'emotion':
328
+ deps.append('@emotion/react')
329
+ elif styling == 'tailwind':
330
+ deps.append('tailwindcss')
331
+
332
+ if state == 'redux':
333
+ deps.extend(['redux', 'react-redux', '@reduxjs/toolkit'])
334
+ elif state == 'mobx':
335
+ deps.extend(['mobx', 'mobx-react-lite'])
336
+ elif state == 'zustand':
337
+ deps.append('zustand')
338
+
339
+ return deps
340
+
341
+ def _generate_widget_config(self, kwargs: Dict[str, Any]) -> List[Dict[str, Any]]:
342
+ """Generate widget configurations."""
343
+ return [
344
+ {'type': 'stats', 'size': 'col-span-3', 'data_source': 'metrics'},
345
+ {'type': 'chart', 'size': 'col-span-8', 'chart_type': 'line'},
346
+ {'type': 'table', 'size': 'col-span-12', 'sortable': True, 'filterable': True}
347
+ ]
348
+
349
+ def _generate_filter_config(self, kwargs: Dict[str, Any]) -> Dict[str, Any]:
350
+ """Generate filter configuration."""
351
+ return {
352
+ 'date_range': True,
353
+ 'search': True,
354
+ 'categories': kwargs.get('filter_categories', []),
355
+ 'custom_filters': kwargs.get('custom_filters', [])
356
+ }
357
+
358
+ def _infer_field_type(self, field: str) -> str:
359
+ """Infer input field type from name."""
360
+ if 'email' in field.lower():
361
+ return 'email'
362
+ elif 'password' in field.lower():
363
+ return 'password'
364
+ elif 'phone' in field.lower():
365
+ return 'tel'
366
+ elif 'date' in field.lower():
367
+ return 'date'
368
+ elif 'number' in field.lower() or 'age' in field.lower():
369
+ return 'number'
370
+ elif 'url' in field.lower() or 'website' in field.lower():
371
+ return 'url'
372
+ return 'text'
373
+
374
+ def _generate_validation_rules(self, field: str) -> Dict[str, Any]:
375
+ """Generate validation rules for field."""
376
+ rules = {'required': True}
377
+
378
+ if 'email' in field.lower():
379
+ rules['email'] = True
380
+ elif 'password' in field.lower():
381
+ rules['minLength'] = 8
382
+ rules['pattern'] = '^(?=.*[A-Za-z])(?=.*\\d)[A-Za-z\\d]{8,}$'
383
+ elif 'age' in field.lower():
384
+ rules['min'] = 0
385
+ rules['max'] = 150
386
+
387
+ return rules
388
+
389
+ def _generate_flex_layout(self, kwargs: Dict[str, Any]) -> Dict[str, Any]:
390
+ """Generate flexbox layout config."""
391
+ return {
392
+ 'type': 'flex',
393
+ 'direction': kwargs.get('direction', 'row'),
394
+ 'justify': kwargs.get('justify', 'space-between'),
395
+ 'align': kwargs.get('align', 'center'),
396
+ 'wrap': kwargs.get('wrap', True),
397
+ 'gap': kwargs.get('gap', 16)
398
+ }
399
+
400
+ def _generate_grid_layout(self, kwargs: Dict[str, Any]) -> Dict[str, Any]:
401
+ """Generate CSS grid layout config."""
402
+ return {
403
+ 'type': 'grid',
404
+ 'columns': kwargs.get('columns', 12),
405
+ 'rows': kwargs.get('rows', 'auto'),
406
+ 'gap': kwargs.get('gap', 16),
407
+ 'areas': kwargs.get('areas', [])
408
+ }
409
+
410
+ def _generate_masonry_layout(self, kwargs: Dict[str, Any]) -> Dict[str, Any]:
411
+ """Generate masonry layout config."""
412
+ return {
413
+ 'type': 'masonry',
414
+ 'columns': kwargs.get('columns', 3),
415
+ 'gap': kwargs.get('gap', 16),
416
+ 'responsive': True
417
+ }
418
+
419
+ def _generate_split_layout(self, kwargs: Dict[str, Any]) -> Dict[str, Any]:
420
+ """Generate split pane layout config."""
421
+ return {
422
+ 'type': 'split',
423
+ 'orientation': kwargs.get('orientation', 'horizontal'),
424
+ 'ratio': kwargs.get('ratio', 0.5),
425
+ 'resizable': kwargs.get('resizable', True),
426
+ 'min_size': kwargs.get('min_size', 200)
427
+ }
428
+
429
+ def _generate_neutral_scale(self) -> Dict[str, str]:
430
+ """Generate neutral color scale."""
431
+ return {
432
+ '50': '#fafafa',
433
+ '100': '#f5f5f5',
434
+ '200': '#e5e5e5',
435
+ '300': '#d4d4d4',
436
+ '400': '#a3a3a3',
437
+ '500': '#737373',
438
+ '600': '#525252',
439
+ '700': '#404040',
440
+ '800': '#262626',
441
+ '900': '#171717'
442
+ }
443
+
444
+ def _generate_type_scale(self) -> Dict[str, str]:
445
+ """Generate typographic scale."""
446
+ return {
447
+ 'xs': '0.75rem',
448
+ 'sm': '0.875rem',
449
+ 'base': '1rem',
450
+ 'lg': '1.125rem',
451
+ 'xl': '1.25rem',
452
+ '2xl': '1.5rem',
453
+ '3xl': '1.875rem',
454
+ '4xl': '2.25rem',
455
+ '5xl': '3rem'
456
+ }
457
+
458
+ def _generate_spacing_scale(self) -> Dict[str, str]:
459
+ """Generate spacing scale."""
460
+ return {
461
+ '0': '0',
462
+ '1': '0.25rem',
463
+ '2': '0.5rem',
464
+ '3': '0.75rem',
465
+ '4': '1rem',
466
+ '5': '1.25rem',
467
+ '6': '1.5rem',
468
+ '8': '2rem',
469
+ '10': '2.5rem',
470
+ '12': '3rem',
471
+ '16': '4rem'
472
+ }
473
+
474
+ def _generate_shadow_scale(self) -> Dict[str, str]:
475
+ """Generate box shadow scale."""
476
+ return {
477
+ 'sm': '0 1px 2px 0 rgb(0 0 0 / 0.05)',
478
+ 'md': '0 4px 6px -1px rgb(0 0 0 / 0.1)',
479
+ 'lg': '0 10px 15px -3px rgb(0 0 0 / 0.1)',
480
+ 'xl': '0 20px 25px -5px rgb(0 0 0 / 0.1)',
481
+ '2xl': '0 25px 50px -12px rgb(0 0 0 / 0.25)'
482
+ }
483
+
484
+ def _generate_fluid_typography(self) -> Dict[str, str]:
485
+ """Generate fluid typography using clamp()."""
486
+ return {
487
+ 'xs': 'clamp(0.75rem, 0.7rem + 0.25vw, 0.875rem)',
488
+ 'sm': 'clamp(0.875rem, 0.825rem + 0.25vw, 1rem)',
489
+ 'base': 'clamp(1rem, 0.95rem + 0.25vw, 1.125rem)',
490
+ 'lg': 'clamp(1.125rem, 1.05rem + 0.375vw, 1.25rem)',
491
+ 'xl': 'clamp(1.25rem, 1.15rem + 0.5vw, 1.5rem)',
492
+ '2xl': 'clamp(1.5rem, 1.35rem + 0.75vw, 1.875rem)'
493
+ }
494
+
495
+
496
+ def get_module() -> ModuleK:
497
+ return ModuleK()