tina4ruby 3.10.32 → 3.10.38

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.
@@ -616,16 +616,71 @@ module Tina4
616
616
 
617
617
  toolbar = <<~HTML.strip
618
618
  <div id="tina4-dev-toolbar" style="position:fixed;bottom:0;left:0;right:0;background:#333;color:#fff;font-family:monospace;font-size:12px;padding:6px 16px;z-index:99999;display:flex;align-items:center;gap:16px;">
619
- <span style="color:#d32f2f;font-weight:bold;">Tina4 v#{version}</span>
619
+ <span id="tina4-ver-btn" style="color:#d32f2f;font-weight:bold;cursor:pointer;text-decoration:underline dotted;" onclick="tina4VersionModal()" title="Click to check for updates">Tina4 v#{version}</span>
620
+ <div id="tina4-ver-modal" style="display:none;position:fixed;bottom:3rem;left:1rem;background:#1e1e2e;border:1px solid #d32f2f;border-radius:8px;padding:16px 20px;z-index:100000;min-width:320px;box-shadow:0 8px 32px rgba(0,0,0,0.5);font-family:monospace;font-size:13px;color:#cdd6f4;">
621
+ <div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:12px;">
622
+ <strong style="color:#89b4fa;">Version Info</strong>
623
+ <span onclick="document.getElementById('tina4-ver-modal').style.display='none'" style="cursor:pointer;color:#888;">&times;</span>
624
+ </div>
625
+ <div id="tina4-ver-body" style="line-height:1.8;">
626
+ <div>Current: <strong style="color:#a6e3a1;">v#{version}</strong></div>
627
+ <div id="tina4-ver-latest" style="color:#888;">Checking for updates...</div>
628
+ </div>
629
+ </div>
620
630
  <span style="color:#4caf50;">#{method}</span>
621
631
  <span>#{path}</span>
622
632
  <span style="color:#666;">&rarr; #{matched_pattern}</span>
623
633
  <span style="color:#ffeb3b;">req:#{request_id}</span>
624
634
  <span style="color:#90caf9;">#{route_count} routes</span>
625
635
  <span style="color:#888;">Ruby #{RUBY_VERSION}</span>
626
- <a href="#" onclick="(function(e){e.preventDefault();var p=document.getElementById('tina4-dev-panel');if(p){p.style.display=p.style.display==='none'?'block':'none';return;}var c=document.createElement('div');c.id='tina4-dev-panel';c.style.cssText='position:fixed;bottom:2rem;right:1rem;width:min(90vw,1200px);height:min(80vh,700px);z-index:99998;transition:all 0.2s';var f=document.createElement('iframe');f.src='/__dev';f.style.cssText='width:100%;height:100%;border:1px solid #CC342D;border-radius:0.5rem;box-shadow:0 8px 32px rgba(0,0,0,0.5);background:#0f172a';c.appendChild(f);document.body.appendChild(c);})(event)" style="color:#ef9a9a;margin-left:auto;text-decoration:none;cursor:pointer;">Dashboard &#8599;</a>
636
+ <a href="#" onclick="(function(e){e.preventDefault();var p=document.getElementById('tina4-dev-panel');if(p){p.style.display=p.style.display==='none'?'block':'none';return;}var c=document.createElement('div');c.id='tina4-dev-panel';c.style.cssText='position:fixed;top:3rem;left:0;right:0;bottom:2rem;z-index:99998;transition:all 0.2s';var f=document.createElement('iframe');f.src='/__dev';f.style.cssText='width:100%;height:100%;border:1px solid #CC342D;border-radius:0.5rem;box-shadow:0 8px 32px rgba(0,0,0,0.5);background:#0f172a';c.appendChild(f);document.body.appendChild(c);})(event)" style="color:#ef9a9a;margin-left:auto;text-decoration:none;cursor:pointer;">Dashboard &#8599;</a>
627
637
  <span onclick="this.parentElement.style.display='none'" style="cursor:pointer;color:#888;margin-left:8px;">&#10005;</span>
628
638
  </div>
639
+ <script>
640
+ function tina4VersionModal(){
641
+ var m=document.getElementById('tina4-ver-modal');
642
+ if(m.style.display==='block'){m.style.display='none';return;}
643
+ m.style.display='block';
644
+ var el=document.getElementById('tina4-ver-latest');
645
+ el.innerHTML='Checking for updates...';
646
+ el.style.color='#888';
647
+ fetch('/__dev/api/version-check')
648
+ .then(function(r){return r.json()})
649
+ .then(function(d){
650
+ var latest=d.latest;
651
+ var current=d.current;
652
+ if(latest===current){
653
+ el.innerHTML='Latest: <strong style="color:#a6e3a1;">v'+latest+'</strong> &mdash; You are up to date!';
654
+ el.style.color='#a6e3a1';
655
+ }else{
656
+ var cParts=current.split('.').map(Number);
657
+ var lParts=latest.split('.').map(Number);
658
+ var isNewer=false;
659
+ for(var i=0;i<Math.max(cParts.length,lParts.length);i++){
660
+ var c=cParts[i]||0,l=lParts[i]||0;
661
+ if(l>c){isNewer=true;break;}
662
+ if(l<c)break;
663
+ }
664
+ if(isNewer){
665
+ var breaking=(lParts[0]!==cParts[0]||lParts[1]!==cParts[1]);
666
+ el.innerHTML='Latest: <strong style="color:#f9e2af;">v'+latest+'</strong>';
667
+ if(breaking){
668
+ el.innerHTML+='<div style="color:#f38ba8;margin-top:6px;">&#9888; Major/minor version change &mdash; check the <a href="https://github.com/tina4stack/tina4-ruby/releases" target="_blank" style="color:#89b4fa;">changelog</a> for breaking changes before upgrading.</div>';
669
+ }else{
670
+ el.innerHTML+='<div style="color:#f9e2af;margin-top:6px;">Patch update available. Run: <code style="background:#313244;padding:2px 6px;border-radius:3px;">gem install tina4ruby</code></div>';
671
+ }
672
+ }else{
673
+ el.innerHTML='Latest: <strong style="color:#a6e3a1;">v'+latest+'</strong> &mdash; You are up to date!';
674
+ el.style.color='#a6e3a1';
675
+ }
676
+ }
677
+ })
678
+ .catch(function(){
679
+ el.innerHTML='Could not check for updates (offline?)';
680
+ el.style.color='#f38ba8';
681
+ });
682
+ }
683
+ </script>
629
684
  HTML
630
685
 
631
686
  if body.include?("</body>")
data/lib/tina4/request.rb CHANGED
@@ -3,6 +3,38 @@ require "uri"
3
3
  require "json"
4
4
 
5
5
  module Tina4
6
+ # A Hash subclass that supports indifferent access (both string and symbol keys).
7
+ # Used by Request#params so that params[:id] and params["id"] both work.
8
+ class IndifferentHash < Hash
9
+ def [](key)
10
+ super(convert_key(key))
11
+ end
12
+
13
+ def []=(key, value)
14
+ super(convert_key(key), value)
15
+ end
16
+
17
+ def fetch(key, *args, &block)
18
+ super(convert_key(key), *args, &block)
19
+ end
20
+
21
+ def key?(key)
22
+ super(convert_key(key))
23
+ end
24
+ alias has_key? key?
25
+ alias include? key?
26
+
27
+ def delete(key, &block)
28
+ super(convert_key(key), &block)
29
+ end
30
+
31
+ private
32
+
33
+ def convert_key(key)
34
+ key.is_a?(Symbol) ? key.to_s : key
35
+ end
36
+ end
37
+
6
38
  class Request
7
39
  attr_reader :env, :method, :path, :query_string, :content_type,
8
40
  :path_params, :ip
@@ -88,10 +120,16 @@ module Tina4
88
120
  end
89
121
 
90
122
  # Merged params: query + body + path_params (path_params highest priority)
123
+ # Supports both string and symbol key access (indifferent access).
91
124
  def params
92
125
  @params ||= build_params
93
126
  end
94
127
 
128
+ # Look up a param by symbol or string key (indifferent access shortcut).
129
+ def param(key)
130
+ params[key.to_s] || params[key.to_sym]
131
+ end
132
+
95
133
  def [](key)
96
134
  params[key.to_s] || params[key.to_sym] || @path_params[key.to_sym]
97
135
  end
@@ -168,10 +206,10 @@ module Tina4
168
206
  end
169
207
 
170
208
  def build_params
171
- p = {}
209
+ p = IndifferentHash.new
172
210
 
173
211
  # Query string params
174
- query.each { |k, v| p[k] = v }
212
+ query.each { |k, v| p[k.to_s] = v }
175
213
 
176
214
  # Body params
177
215
  body_parsed.each { |k, v| p[k.to_s] = v }
@@ -103,10 +103,15 @@ module Tina4
103
103
  self
104
104
  end
105
105
 
106
- def render(template_path, data = {}, status: 200)
106
+ def render(template_path, data = {}, status: 200, template_dir: nil)
107
107
  @status_code = status
108
108
  @headers["content-type"] = HTML_CONTENT_TYPE
109
- @body = Tina4::Template.render(template_path, data)
109
+ if template_dir
110
+ frond = Tina4::Frond.new(template_dir: template_dir)
111
+ @body = frond.render(template_path, data)
112
+ else
113
+ @body = Tina4::Template.render(template_path, data)
114
+ end
110
115
  self
111
116
  end
112
117
 
data/lib/tina4/router.rb CHANGED
@@ -89,10 +89,14 @@ module Tina4
89
89
  parts = path.split("/").reject(&:empty?)
90
90
  regex_parts = parts.map do |part|
91
91
  if part =~ /\A\*(\w+)\z/
92
- # Catch-all splat parameter: *path captures everything after
92
+ # Named catch-all splat parameter: *path captures everything after
93
93
  name = Regexp.last_match(1)
94
94
  @param_names << { name: name.to_sym, type: "path" }
95
95
  '(.+)'
96
+ elsif part == "*"
97
+ # Bare catch-all wildcard: captures everything after (unnamed)
98
+ @param_names << { name: :splat, type: "path" }
99
+ '(.+)'
96
100
  elsif part =~ /\A\{(\w+)(?::(\w+))?\}\z/
97
101
  # Tina4/Python-style brace params: {id} or {id:int}
98
102
  # This is the ONLY supported param syntax, matching Python exactly.
data/lib/tina4/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Tina4
4
- VERSION = "3.10.32"
4
+ VERSION = "3.10.38"
5
5
  end
metadata CHANGED
@@ -1,13 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: tina4ruby
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.10.32
4
+ version: 3.10.38
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tina4 Team
8
+ autorequire:
8
9
  bindir: exe
9
10
  cert_chain: []
10
- date: 1980-01-02 00:00:00.000000000 Z
11
+ date: 2026-04-01 00:00:00.000000000 Z
11
12
  dependencies:
12
13
  - !ruby/object:Gem::Dependency
13
14
  name: rack
@@ -170,7 +171,7 @@ dependencies:
170
171
  - - "~>"
171
172
  - !ruby/object:Gem::Version
172
173
  version: '1.8'
173
- type: :development
174
+ type: :runtime
174
175
  prerelease: false
175
176
  version_requirements: !ruby/object:Gem::Requirement
176
177
  requirements:
@@ -323,6 +324,7 @@ files:
323
324
  - lib/tina4/log.rb
324
325
  - lib/tina4/mcp.rb
325
326
  - lib/tina4/messenger.rb
327
+ - lib/tina4/metrics.rb
326
328
  - lib/tina4/middleware.rb
327
329
  - lib/tina4/migration.rb
328
330
  - lib/tina4/orm.rb
@@ -401,6 +403,7 @@ licenses:
401
403
  - MIT
402
404
  metadata:
403
405
  homepage_uri: https://tina4.com
406
+ post_install_message:
404
407
  rdoc_options: []
405
408
  require_paths:
406
409
  - lib
@@ -415,7 +418,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
415
418
  - !ruby/object:Gem::Version
416
419
  version: '0'
417
420
  requirements: []
418
- rubygems_version: 4.0.3
421
+ rubygems_version: 3.4.19
422
+ signing_key:
419
423
  specification_version: 4
420
424
  summary: Simple. Fast. Human. This is not a framework.
421
425
  test_files: []