antigravity-rtl-patcher 2.2.1 → 2.3.0

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.
package/bin/cli.js CHANGED
@@ -5,13 +5,13 @@ const chalk = require('chalk');
5
5
  const { patch, restore, status } = require('../src/patcher');
6
6
 
7
7
  const BANNER = `
8
- ${chalk.cyan('Antigravity Smart RTL Patcher')} ${chalk.gray('v2.2.0')}
8
+ ${chalk.cyan('Antigravity Smart RTL Patcher')} ${chalk.gray('v2.3.0')}
9
9
  `;
10
10
 
11
11
  program
12
12
  .name('agy-rtl')
13
13
  .description('RTL patcher for Antigravity')
14
- .version('2.2.0');
14
+ .version('2.3.0');
15
15
 
16
16
  program
17
17
  .command('patch')
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "antigravity-rtl-patcher",
3
- "version": "2.2.1",
3
+ "version": "2.3.0",
4
4
  "description": "Smart RTL and Persian/Arabic typography patcher for Antigravity",
5
5
  "main": "src/patcher.js",
6
6
  "bin": {
@@ -1,7 +1,9 @@
1
1
  /**
2
2
  * AGY RTL — Preload Injection Snippet
3
3
  * Appended to Antigravity's preload.js at patch time.
4
- * Injects the RTL engine + toggle panel into every renderer page.
4
+ *
5
+ * Only applies RTL direction to individual text elements containing
6
+ * Persian/Arabic/Hebrew characters. Does NOT change the app layout.
5
7
  *
6
8
  * MARKER: __AGY_RTL_INJECTED__
7
9
  */
@@ -30,51 +32,28 @@ function _agyRtlRendererMain() {
30
32
 
31
33
  var RTL_REGEX = /[\u0591-\u07FF\u200F\u202B\u202E\uFB1D-\uFDFD\uFE70-\uFEFC]/;
32
34
  var RTL_CHAR_REGEX = /[\u0591-\u07FF\uFB1D-\uFDFD\uFE70-\uFEFC]/g;
33
- var MIN_RTL_RATIO = 0.3;
35
+ var MIN_RTL_RATIO = 0.25;
34
36
 
35
37
  // ─── State ───────────────────────────────────────────────
36
38
  var isEnabled = getStoredState();
37
39
  var observer = null;
38
40
  var panelVisible = false;
41
+ var hoverTimeout = null;
39
42
 
40
43
  // ─── CSS ─────────────────────────────────────────────────
41
44
  var CSS = `
42
45
  @import url('https://fonts.googleapis.com/css2?family=Vazirmatn:wght@300;400;500;700&display=swap');
43
46
 
44
- /* === Global RTL Mode === */
45
- html.agy-rtl-active {
46
- direction: rtl !important;
47
- }
48
- html.agy-rtl-active body {
47
+ /* RTL styling applied per-element, NOT globally */
48
+ [${PROCESSED_ATTR}="true"] {
49
49
  direction: rtl !important;
50
50
  text-align: right !important;
51
- }
52
-
53
- /* Keep code and technical elements LTR */
54
- html.agy-rtl-active code,
55
- html.agy-rtl-active pre,
56
- html.agy-rtl-active .monaco-editor,
57
- html.agy-rtl-active .monaco-tokenized-source,
58
- html.agy-rtl-active [class*="CodeMirror"],
59
- html.agy-rtl-active input[type="text"],
60
- html.agy-rtl-active input[type="url"],
61
- html.agy-rtl-active input[type="email"],
62
- html.agy-rtl-active textarea.inputarea,
63
- html.agy-rtl-active .terminal,
64
- html.agy-rtl-active .xterm {
65
- direction: ltr !important;
66
- text-align: left !important;
67
- unicode-bidi: isolate !important;
68
- }
69
-
70
- /* Font for RTL text */
71
- html.agy-rtl-active [${PROCESSED_ATTR}="true"] {
51
+ unicode-bidi: plaintext !important;
72
52
  font-family: 'Vazirmatn', system-ui, -apple-system, 'Segoe UI', sans-serif !important;
73
- line-height: 1.85 !important;
74
- word-spacing: 0.02em;
53
+ line-height: 1.8 !important;
75
54
  }
76
55
 
77
- /* Preserve code inside RTL blocks */
56
+ /* Keep code LTR inside RTL blocks */
78
57
  [${PROCESSED_ATTR}="true"] code,
79
58
  [${PROCESSED_ATTR}="true"] pre {
80
59
  direction: ltr !important;
@@ -95,40 +74,42 @@ function _agyRtlRendererMain() {
95
74
  /* === Status Bar Icon === */
96
75
  .agy-rtl-status-icon {
97
76
  position: fixed;
98
- bottom: 2px;
99
- right: 70px;
77
+ bottom: 6px;
78
+ right: 16px;
100
79
  z-index: 99999;
101
80
  display: flex;
102
81
  align-items: center;
103
82
  justify-content: center;
104
- gap: 4px;
105
- height: 20px;
106
- padding: 0 8px;
83
+ gap: 6px;
84
+ height: 28px;
85
+ padding: 0 12px;
107
86
  cursor: pointer;
108
- border-radius: 3px;
87
+ border-radius: 14px;
109
88
  user-select: none;
110
89
  -webkit-user-select: none;
111
- transition: background .15s, opacity .15s;
112
- opacity: .75;
90
+ transition: background .2s, opacity .2s, box-shadow .2s;
91
+ opacity: .6;
113
92
  font-family: -apple-system, 'Segoe UI', system-ui, sans-serif;
114
- font-size: 11px;
115
- font-weight: 500;
116
- letter-spacing: .02em;
117
- color: #999;
118
- background: transparent;
93
+ font-size: 12px;
94
+ font-weight: 600;
95
+ letter-spacing: .03em;
96
+ color: #888;
97
+ background: rgba(128,128,128,.08);
119
98
  }
120
99
  .agy-rtl-status-icon:hover {
121
100
  opacity: 1;
122
- background: rgba(255,255,255,.08);
123
- color: #ddd;
101
+ background: rgba(128,128,128,.15);
102
+ color: #aaa;
124
103
  }
125
104
  .agy-rtl-status-icon.active {
126
- color: #6cb6ff;
127
- opacity: .9;
105
+ color: #5ba0d6;
106
+ opacity: .85;
107
+ background: rgba(91,160,214,.08);
128
108
  }
129
109
  .agy-rtl-status-icon.active:hover {
130
110
  opacity: 1;
131
- color: #8ecaff;
111
+ background: rgba(91,160,214,.15);
112
+ box-shadow: 0 0 12px rgba(91,160,214,.12);
132
113
  }
133
114
  .agy-rtl-status-icon svg {
134
115
  flex-shrink: 0;
@@ -138,20 +119,22 @@ function _agyRtlRendererMain() {
138
119
  .agy-rtl-panel {
139
120
  position: fixed;
140
121
  z-index: 100000;
141
- width: 280px;
122
+ width: 260px;
142
123
  background: #1e1e1e;
143
- border: 1px solid #383838;
124
+ border: 1px solid #333;
144
125
  border-radius: 10px;
145
126
  box-shadow: 0 12px 40px rgba(0,0,0,.5), 0 0 0 1px rgba(255,255,255,.04);
146
127
  padding: 0;
147
128
  opacity: 0;
148
129
  visibility: hidden;
149
130
  transform: translateY(6px);
150
- transition: opacity .18s ease, transform .18s ease, visibility .18s ease;
131
+ transition: opacity .15s ease, transform .15s ease, visibility .15s ease;
151
132
  font-family: -apple-system, 'Segoe UI', system-ui, sans-serif;
152
133
  font-size: 13px;
153
134
  color: #ccc;
154
135
  overflow: hidden;
136
+ direction: ltr !important;
137
+ text-align: left !important;
155
138
  }
156
139
  .agy-rtl-panel.visible {
157
140
  opacity: 1;
@@ -181,11 +164,6 @@ function _agyRtlRendererMain() {
181
164
  font-size: 13px;
182
165
  color: #b0b0b0;
183
166
  }
184
- .agy-rtl-panel-sep {
185
- height: 1px;
186
- background: #2a2a2a;
187
- margin: 4px 0;
188
- }
189
167
 
190
168
  /* Toggle */
191
169
  .agy-rtl-switch {
@@ -234,7 +212,7 @@ function _agyRtlRendererMain() {
234
212
  display: inline-flex;
235
213
  align-items: center;
236
214
  gap: 5px;
237
- color: #6c7986;
215
+ color: #555;
238
216
  text-decoration: none;
239
217
  font-size: 11px;
240
218
  transition: color .15s;
@@ -261,23 +239,32 @@ function _agyRtlRendererMain() {
261
239
  }
262
240
  function applyRTL(el) {
263
241
  if (el.getAttribute(PROCESSED_ATTR)) return;
264
- if (isRTL(el.textContent || '')) {
242
+ // Skip the RTL panel/icon itself
243
+ if (el.closest && (el.closest('#' + PANEL_ID) || el.closest('#' + STATUS_ICON_ID))) return;
244
+ var text = el.textContent || '';
245
+ if (text.trim().length > 0 && isRTL(text)) {
265
246
  el.setAttribute(PROCESSED_ATTR, 'true');
266
- el.style.unicodeBidi = 'plaintext';
267
247
  }
268
248
  }
269
249
  function removeRTL(el) {
270
250
  if (el.getAttribute(PROCESSED_ATTR)) {
271
251
  el.removeAttribute(PROCESSED_ATTR);
272
- el.style.unicodeBidi = '';
273
252
  }
274
253
  }
275
254
 
276
- // Target all text-bearing elements
277
- var SELECTORS = 'p,li,h1,h2,h3,h4,h5,h6,blockquote,td,th,span,div,a,label,button,[class*="title"],[class*="label"],[class*="name"],[class*="text"],[class*="message"],[class*="content"],[class*="description"]';
255
+ // Wide selectors — catch all text-bearing elements
256
+ var SELECTORS = [
257
+ 'p', 'li', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6',
258
+ 'blockquote', 'td', 'th', 'span', 'a', 'label',
259
+ 'div', 'button',
260
+ ].join(',');
278
261
 
279
262
  function scanElement(root) {
280
263
  if (!root || root.nodeType !== 1) return;
264
+ // Skip our own UI
265
+ if (root.id === PANEL_ID || root.id === STATUS_ICON_ID) return;
266
+ if (root.closest && (root.closest('#' + PANEL_ID) || root.closest('#' + STATUS_ICON_ID))) return;
267
+
281
268
  try { if (root.matches && root.matches(SELECTORS)) applyRTL(root); } catch (e) {}
282
269
  try { root.querySelectorAll(SELECTORS).forEach(applyRTL); } catch (e) {}
283
270
  }
@@ -289,9 +276,6 @@ function _agyRtlRendererMain() {
289
276
 
290
277
  function startObserver() {
291
278
  if (observer) return;
292
- // Apply global RTL
293
- document.documentElement.classList.add('agy-rtl-active');
294
-
295
279
  observer = new MutationObserver(function (muts) {
296
280
  if (!isEnabled) return;
297
281
  muts.forEach(function (m) {
@@ -305,7 +289,6 @@ function _agyRtlRendererMain() {
305
289
 
306
290
  function stopObserver() {
307
291
  if (observer) { observer.disconnect(); observer = null; }
308
- document.documentElement.classList.remove('agy-rtl-active');
309
292
  clearAllRTL();
310
293
  }
311
294
 
@@ -327,10 +310,10 @@ function _agyRtlRendererMain() {
327
310
  icon.className = 'agy-rtl-status-icon' + (isEnabled ? ' active' : '');
328
311
  icon.setAttribute('title', 'Antigravity Smart RTL');
329
312
 
330
- // Globe + arrow SVG icon
313
+ // Globe SVG
331
314
  var svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
332
- svg.setAttribute('width', '14');
333
- svg.setAttribute('height', '14');
315
+ svg.setAttribute('width', '15');
316
+ svg.setAttribute('height', '15');
334
317
  svg.setAttribute('viewBox', '0 0 24 24');
335
318
  svg.setAttribute('fill', 'none');
336
319
  svg.setAttribute('stroke', 'currentColor');
@@ -338,33 +321,41 @@ function _agyRtlRendererMain() {
338
321
  svg.setAttribute('stroke-linecap', 'round');
339
322
  svg.setAttribute('stroke-linejoin', 'round');
340
323
 
341
- // Globe circle
342
324
  var circle = document.createElementNS('http://www.w3.org/2000/svg', 'circle');
343
- circle.setAttribute('cx', '12');
344
- circle.setAttribute('cy', '12');
345
- circle.setAttribute('r', '10');
325
+ circle.setAttribute('cx', '12'); circle.setAttribute('cy', '12'); circle.setAttribute('r', '10');
346
326
  svg.appendChild(circle);
347
-
348
- // Globe horizontal line
349
327
  var line1 = document.createElementNS('http://www.w3.org/2000/svg', 'line');
350
328
  line1.setAttribute('x1', '2'); line1.setAttribute('y1', '12');
351
329
  line1.setAttribute('x2', '22'); line1.setAttribute('y2', '12');
352
330
  svg.appendChild(line1);
353
-
354
- // Globe vertical ellipse
355
- var ellipse = document.createElementNS('http://www.w3.org/2000/svg', 'path');
356
- ellipse.setAttribute('d', 'M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z');
357
- svg.appendChild(ellipse);
358
-
331
+ var ell = document.createElementNS('http://www.w3.org/2000/svg', 'path');
332
+ ell.setAttribute('d', 'M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z');
333
+ svg.appendChild(ell);
359
334
  icon.appendChild(svg);
360
335
 
361
- // Label text
362
336
  var lbl = document.createElement('span');
363
337
  lbl.textContent = 'RTL';
364
338
  lbl.style.cssText = 'direction:ltr!important;';
365
339
  icon.appendChild(lbl);
366
340
 
367
- icon.addEventListener('click', function (e) { e.stopPropagation(); togglePanel(); });
341
+ // Hover to open panel
342
+ icon.addEventListener('mouseenter', function () {
343
+ clearTimeout(hoverTimeout);
344
+ hoverTimeout = setTimeout(function () { showPanel(); }, 200);
345
+ });
346
+ icon.addEventListener('mouseleave', function () {
347
+ clearTimeout(hoverTimeout);
348
+ hoverTimeout = setTimeout(function () {
349
+ var panel = document.getElementById(PANEL_ID);
350
+ if (panel && !panel.matches(':hover')) hidePanel();
351
+ }, 300);
352
+ });
353
+ // Also toggle on click
354
+ icon.addEventListener('click', function (e) {
355
+ e.stopPropagation();
356
+ if (panelVisible) hidePanel(); else showPanel();
357
+ });
358
+
368
359
  document.body.appendChild(icon);
369
360
  }
370
361
 
@@ -374,7 +365,12 @@ function _agyRtlRendererMain() {
374
365
  var panel = document.createElement('div');
375
366
  panel.id = PANEL_ID;
376
367
  panel.className = 'agy-rtl-panel';
377
- panel.style.cssText = 'direction:ltr!important;text-align:left!important;';
368
+
369
+ // Keep panel open on hover
370
+ panel.addEventListener('mouseenter', function () { clearTimeout(hoverTimeout); });
371
+ panel.addEventListener('mouseleave', function () {
372
+ hoverTimeout = setTimeout(function () { hidePanel(); }, 300);
373
+ });
378
374
 
379
375
  // Header
380
376
  var header = document.createElement('div');
@@ -389,7 +385,6 @@ function _agyRtlRendererMain() {
389
385
  var body = document.createElement('div');
390
386
  body.className = 'agy-rtl-panel-body';
391
387
 
392
- // Toggle row
393
388
  var row = document.createElement('div');
394
389
  row.className = 'agy-rtl-panel-row';
395
390
  var label = document.createElement('span');
@@ -411,7 +406,7 @@ function _agyRtlRendererMain() {
411
406
  body.appendChild(row);
412
407
  panel.appendChild(body);
413
408
 
414
- // Footer with GitHub
409
+ // Footer
415
410
  var footer = document.createElement('div');
416
411
  footer.className = 'agy-rtl-panel-footer';
417
412
  var ghLink = document.createElement('a');
@@ -421,10 +416,8 @@ function _agyRtlRendererMain() {
421
416
  ghLink.rel = 'noopener noreferrer';
422
417
 
423
418
  var ghSvg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
424
- ghSvg.setAttribute('width', '12');
425
- ghSvg.setAttribute('height', '12');
426
- ghSvg.setAttribute('viewBox', '0 0 16 16');
427
- ghSvg.setAttribute('fill', 'currentColor');
419
+ ghSvg.setAttribute('width', '12'); ghSvg.setAttribute('height', '12');
420
+ ghSvg.setAttribute('viewBox', '0 0 16 16'); ghSvg.setAttribute('fill', 'currentColor');
428
421
  var ghP = document.createElementNS('http://www.w3.org/2000/svg', 'path');
429
422
  ghP.setAttribute('d', 'M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0016 8c0-4.42-3.58-8-8-8z');
430
423
  ghSvg.appendChild(ghP);
@@ -435,35 +428,35 @@ function _agyRtlRendererMain() {
435
428
  footer.appendChild(ghLink);
436
429
  panel.appendChild(footer);
437
430
 
438
- // Events
431
+ // Toggle event
439
432
  cb.addEventListener('change', function () {
440
433
  isEnabled = cb.checked;
441
434
  setStoredState(isEnabled);
442
435
  updateState();
443
436
  });
444
437
 
438
+ // Close on Escape
439
+ document.addEventListener('keydown', function (e) {
440
+ if (e.key === 'Escape' && panelVisible) hidePanel();
441
+ });
442
+
443
+ // Close on outside click
445
444
  document.addEventListener('click', function (e) {
446
445
  var p = document.getElementById(PANEL_ID);
447
446
  var ic = document.getElementById(STATUS_ICON_ID);
448
447
  if (p && panelVisible && !p.contains(e.target) && !ic.contains(e.target)) hidePanel();
449
448
  });
450
449
 
451
- document.addEventListener('keydown', function (e) {
452
- if (e.key === 'Escape' && panelVisible) hidePanel();
453
- });
454
-
455
450
  document.body.appendChild(panel);
456
451
  }
457
452
 
458
- function togglePanel() { panelVisible ? hidePanel() : showPanel(); }
459
-
460
453
  function showPanel() {
461
454
  var panel = document.getElementById(PANEL_ID);
462
455
  if (!panel) { createPanel(); panel = document.getElementById(PANEL_ID); }
463
456
  var icon = document.getElementById(STATUS_ICON_ID);
464
457
  if (icon) {
465
458
  var r = icon.getBoundingClientRect();
466
- panel.style.bottom = (window.innerHeight - r.top + 6) + 'px';
459
+ panel.style.bottom = (window.innerHeight - r.top + 8) + 'px';
467
460
  panel.style.right = (window.innerWidth - r.right) + 'px';
468
461
  }
469
462
  panel.classList.add('visible');