Almirah 0.4.4 → 0.4.6
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.
- checksums.yaml +4 -4
- data/lib/almirah/doc_fabric.rb +0 -6
- data/lib/almirah/doc_items/controlled_table.rb +23 -4
- data/lib/almirah/doc_items/scope_table.rb +39 -88
- data/lib/almirah/doc_types/base_document.rb +14 -22
- data/lib/almirah/doc_types/decision.rb +1 -126
- data/lib/almirah/doc_types/decisions_overview.rb +5 -555
- data/lib/almirah/doc_types/planning_dates.rb +2 -3
- data/lib/almirah/doc_types/protocol.rb +4 -1
- data/lib/almirah/doc_types/risks_overview.rb +6 -6
- data/lib/almirah/doc_types/source_file.rb +2 -1
- data/lib/almirah/project/project_data.rb +1 -4
- data/lib/almirah/project.rb +22 -122
- data/lib/almirah/project_configuration.rb +5 -97
- data/lib/almirah/templates/css/main.css +13 -167
- metadata +1 -8
- data/lib/almirah/doc_items/work_item.rb +0 -129
- data/lib/almirah/doc_types/critical_chain_page.rb +0 -218
- data/lib/almirah/doc_types/decision_grouping.rb +0 -17
- data/lib/almirah/project/critical_chain.rb +0 -117
- data/lib/almirah/project/fever_chart.rb +0 -94
- data/lib/almirah/project/work_item_scheduler.rb +0 -167
- data/lib/almirah/project/working_calendar.rb +0 -112
|
@@ -94,9 +94,10 @@ class SourceFile < PersistentDocument
|
|
|
94
94
|
file.puts s.gsub! '{{DOCUMENT_TITLE}}', @title
|
|
95
95
|
elsif s.include?('{{STYLES_AND_SCRIPTS}}')
|
|
96
96
|
file.puts "<link rel=\"stylesheet\" href=\"#{css_path}\">"
|
|
97
|
+
file.puts font_size_style if BaseDocument.font_size
|
|
97
98
|
file.puts "<script src=\"#{js_path}\"></script>"
|
|
98
99
|
elsif s.include?('{{HOME_BUTTON}}')
|
|
99
|
-
file.puts "<a id=\"index_menu_item\" href=\"#{index_path}\"><span><i class=\"fa fa-
|
|
100
|
+
file.puts "<a id=\"index_menu_item\" href=\"#{index_path}\"><span><i class=\"fa fa-home\" aria-hidden=\"true\"></i></span> Documents</a>"
|
|
100
101
|
elsif s.include?('{{GEM_VERSION}}')
|
|
101
102
|
file.puts "(#{Gem.loaded_specs['Almirah'].version.version})"
|
|
102
103
|
else
|
|
@@ -4,7 +4,7 @@ class ProjectData
|
|
|
4
4
|
attr_reader :specifications, :protocols, :traceability_matrices, :coverage_matrices, :source_files,
|
|
5
5
|
:specifications_dictionary, :covered_specifications_dictionary, :implemented_specifications_dictionary,
|
|
6
6
|
:implementation_matrices, :decisions, :decision_groups, :risk_records, :risk_registries,
|
|
7
|
-
:risk_registry_prefaces, :
|
|
7
|
+
:risk_registry_prefaces, :link_registry
|
|
8
8
|
|
|
9
9
|
def initialize
|
|
10
10
|
@specifications = []
|
|
@@ -25,9 +25,6 @@ class ProjectData
|
|
|
25
25
|
# the registry (first-level risks/ folder) name. A registry without an
|
|
26
26
|
# overview.md has no entry; its page simply starts at the register table.
|
|
27
27
|
@risk_registry_prefaces = {}
|
|
28
|
-
# Every Scope-row WorkItem across all decision records, keyed by its canonical
|
|
29
|
-
# "<record>.<step>.<activity>" id (see ADR-194), populated by link_work_items.
|
|
30
|
-
@work_items = {}
|
|
31
28
|
|
|
32
29
|
@specifications_dictionary = {}
|
|
33
30
|
@covered_specifications_dictionary = {}
|
data/lib/almirah/project.rb
CHANGED
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
require 'fileutils'
|
|
4
4
|
require_relative 'doc_fabric'
|
|
5
|
-
require_relative 'doc_items/work_item'
|
|
6
5
|
require_relative 'navigation_pane'
|
|
7
6
|
require_relative 'doc_types/traceability'
|
|
8
7
|
require_relative 'doc_types/index'
|
|
@@ -19,6 +18,7 @@ class Project
|
|
|
19
18
|
def initialize(configuration)
|
|
20
19
|
@configuration = configuration
|
|
21
20
|
@project_data = ProjectData.new
|
|
21
|
+
BaseDocument.font_size = @configuration.get_font_size
|
|
22
22
|
|
|
23
23
|
@index = nil
|
|
24
24
|
@project = self
|
|
@@ -53,7 +53,6 @@ class Project
|
|
|
53
53
|
link_all_risks
|
|
54
54
|
check_wrong_specification_referenced
|
|
55
55
|
build_link_registry
|
|
56
|
-
link_work_items
|
|
57
56
|
create_index
|
|
58
57
|
render_all_specifications(@project_data.specifications)
|
|
59
58
|
render_all_specifications(@project_data.traceability_matrices)
|
|
@@ -61,8 +60,8 @@ class Project
|
|
|
61
60
|
render_all_protocols
|
|
62
61
|
render_all_source_files
|
|
63
62
|
render_all_specifications(@project_data.implementation_matrices) # intentionally after source file rendering
|
|
63
|
+
copy_decision_and_risk_images
|
|
64
64
|
render_decisions_overview
|
|
65
|
-
render_critical_chain_page
|
|
66
65
|
render_all_decisions
|
|
67
66
|
render_all_risk_records
|
|
68
67
|
render_risk_registry_pages
|
|
@@ -70,7 +69,6 @@ class Project
|
|
|
70
69
|
render_index
|
|
71
70
|
create_search_data
|
|
72
71
|
report_broken_links
|
|
73
|
-
report_kit_violations
|
|
74
72
|
report_rendered
|
|
75
73
|
end
|
|
76
74
|
|
|
@@ -87,7 +85,6 @@ class Project
|
|
|
87
85
|
link_all_risks
|
|
88
86
|
check_wrong_specification_referenced
|
|
89
87
|
build_link_registry
|
|
90
|
-
link_work_items
|
|
91
88
|
create_index
|
|
92
89
|
render_all_specifications(@project_data.specifications)
|
|
93
90
|
render_all_specifications(@project_data.traceability_matrices)
|
|
@@ -95,8 +92,8 @@ class Project
|
|
|
95
92
|
render_all_protocols
|
|
96
93
|
render_all_source_files
|
|
97
94
|
render_all_specifications(@project_data.implementation_matrices) # intentionally after source file rendering
|
|
95
|
+
copy_decision_and_risk_images
|
|
98
96
|
render_decisions_overview
|
|
99
|
-
render_critical_chain_page
|
|
100
97
|
render_all_decisions
|
|
101
98
|
render_all_risk_records
|
|
102
99
|
render_risk_registry_pages
|
|
@@ -104,7 +101,6 @@ class Project
|
|
|
104
101
|
render_index
|
|
105
102
|
create_search_data
|
|
106
103
|
report_broken_links
|
|
107
|
-
report_kit_violations
|
|
108
104
|
report_rendered
|
|
109
105
|
end
|
|
110
106
|
|
|
@@ -124,111 +120,6 @@ class Project
|
|
|
124
120
|
broken.each { |b| puts ConsoleReporter.warn_detail(" #{b[:document] || '?'}: #{b[:target]}") }
|
|
125
121
|
end
|
|
126
122
|
|
|
127
|
-
# Builds the per-row WorkItem dependency network (ADR-194): registers every
|
|
128
|
-
# Scope-row work item, fills the intra-record step-order edges, then resolves
|
|
129
|
-
# each Depends On reference globally (LinkRegistry) to the activity-type-aligned
|
|
130
|
-
# work item of the target record, tagging each cross-record edge in-group or
|
|
131
|
-
# cross-group against the decision_groups boundary (ADR-197). Unresolved
|
|
132
|
-
# references are collected for report_kit_violations. Runs after
|
|
133
|
-
# build_link_registry (so every record resolves) and before rendering (so the
|
|
134
|
-
# overview Kit column is ready).
|
|
135
|
-
def link_work_items
|
|
136
|
-
@kit_unresolved = []
|
|
137
|
-
@project_data.decisions.each do |d|
|
|
138
|
-
d.scope_work_items.each { |wi| @project_data.work_items[wi.id] = wi }
|
|
139
|
-
end
|
|
140
|
-
link_intra_record_steps
|
|
141
|
-
link_cross_record_dependencies
|
|
142
|
-
end
|
|
143
|
-
|
|
144
|
-
# Each row's lower-numbered same-record steps are its predecessors; equal step
|
|
145
|
-
# numbers are concurrent (no edge). These edges are in-group by definition.
|
|
146
|
-
def link_intra_record_steps
|
|
147
|
-
@project_data.decisions.each do |d|
|
|
148
|
-
items = d.scope_work_items
|
|
149
|
-
items.each do |wi|
|
|
150
|
-
items.each do |other|
|
|
151
|
-
next if other.equal?(wi) || other.step >= wi.step
|
|
152
|
-
|
|
153
|
-
wi.add_predecessor(other, cross_group: false)
|
|
154
|
-
other.add_successor(wi)
|
|
155
|
-
end
|
|
156
|
-
end
|
|
157
|
-
end
|
|
158
|
-
end
|
|
159
|
-
|
|
160
|
-
def link_cross_record_dependencies
|
|
161
|
-
@project_data.decisions.each do |d|
|
|
162
|
-
d.scope_work_items.each do |wi|
|
|
163
|
-
wi.depends_on_refs.each { |ref| link_dependency(d, wi, ref) }
|
|
164
|
-
end
|
|
165
|
-
end
|
|
166
|
-
end
|
|
167
|
-
|
|
168
|
-
def link_dependency(record, work_item, ref)
|
|
169
|
-
target = @project_data.link_registry.find_by_id(ref)
|
|
170
|
-
unless target.is_a?(Decision)
|
|
171
|
-
@kit_unresolved << { record: record.id, target: ref }
|
|
172
|
-
return
|
|
173
|
-
end
|
|
174
|
-
prereq = aligned_work_item(target, work_item.activity)
|
|
175
|
-
return if prereq.nil? || prereq.equal?(work_item)
|
|
176
|
-
|
|
177
|
-
cross = decision_group_name(record) != decision_group_name(target)
|
|
178
|
-
work_item.add_predecessor(prereq, cross_group: cross)
|
|
179
|
-
prereq.add_successor(work_item)
|
|
180
|
-
anchor = target.scope_table&.step_column? ? prereq.row_anchor : nil
|
|
181
|
-
work_item.add_resolved_dependency(ref, target, anchor, prereq.id)
|
|
182
|
-
end
|
|
183
|
-
|
|
184
|
-
# The target record's work item whose activity (Item) matches `activity`,
|
|
185
|
-
# falling back to the nearest earlier activity by canonical phase order, then
|
|
186
|
-
# (when the target has only later activities) to its earliest row. nil only
|
|
187
|
-
# when the target has no Scope rows.
|
|
188
|
-
def aligned_work_item(target, activity)
|
|
189
|
-
items = target.scope_work_items
|
|
190
|
-
return nil if items.empty?
|
|
191
|
-
|
|
192
|
-
exact = items.select { |t| t.activity == activity }.min_by(&:step)
|
|
193
|
-
return exact if exact
|
|
194
|
-
|
|
195
|
-
rank = WorkItem::ACTIVITY_ORDER.index(activity) || WorkItem::ACTIVITY_ORDER.length
|
|
196
|
-
earlier = items.select { |t| t.activity_rank <= rank }
|
|
197
|
-
return items.min_by { |t| [t.activity_rank, t.step] } if earlier.empty?
|
|
198
|
-
|
|
199
|
-
earlier.min_by { |t| [-t.activity_rank, t.step] }
|
|
200
|
-
end
|
|
201
|
-
|
|
202
|
-
# The planning-group name (first-level decisions/ folder) a record belongs to,
|
|
203
|
-
# read from the decision_groups collection (ADR-197).
|
|
204
|
-
def decision_group_name(doc)
|
|
205
|
-
group = @project_data.decision_groups.find { |g| g.values.first.include?(doc) }
|
|
206
|
-
group&.keys&.first
|
|
207
|
-
end
|
|
208
|
-
|
|
209
|
-
# Reports the two kit gates and unresolved Depends On references (ADR-194), all
|
|
210
|
-
# as non-failing console warnings alongside report_broken_links.
|
|
211
|
-
def report_kit_violations
|
|
212
|
-
@kit_unresolved ||= []
|
|
213
|
-
phase = @project_data.work_items.each_value.select(&:phase_order_violation?)
|
|
214
|
-
cross = @project_data.work_items.each_value.select(&:cross_record_violation?)
|
|
215
|
-
total = phase.length + cross.length + @kit_unresolved.length
|
|
216
|
-
return if total.zero?
|
|
217
|
-
|
|
218
|
-
ConsoleReporter.warn('kit violations', total)
|
|
219
|
-
phase.each do |wi|
|
|
220
|
-
blocking = wi.intra_record_predecessors.reject(&:done?).map(&:id).join(', ')
|
|
221
|
-
puts ConsoleReporter.warn_detail(" phase order: #{wi.id} started before #{blocking}")
|
|
222
|
-
end
|
|
223
|
-
cross.each do |wi|
|
|
224
|
-
blocking = wi.cross_record_predecessors.reject(&:done?).map(&:id).join(', ')
|
|
225
|
-
puts ConsoleReporter.warn_detail(" not kitted: #{wi.id} needs #{blocking}")
|
|
226
|
-
end
|
|
227
|
-
@kit_unresolved.each do |u|
|
|
228
|
-
puts ConsoleReporter.warn_detail(" unresolved Depends On: #{u[:record]} -> #{u[:target]}")
|
|
229
|
-
end
|
|
230
|
-
end
|
|
231
|
-
|
|
232
123
|
# Assigns each document its generated output path (relative to the build root)
|
|
233
124
|
# and registers it for cross-document link resolution (ADR-186). Runs after all
|
|
234
125
|
# documents are parsed and before any rendering, so link targets are known.
|
|
@@ -585,6 +476,25 @@ class Project
|
|
|
585
476
|
end
|
|
586
477
|
end
|
|
587
478
|
|
|
479
|
+
# Every folder named img under decisions/ and risks/ is copied to the same
|
|
480
|
+
# relative location under build/ (ADR-227, SRS-176), so relative image
|
|
481
|
+
# references in decision records, risk records, and registry prefaces
|
|
482
|
+
# resolve in the rendered HTML. Folder-based rather than doc-id-based like
|
|
483
|
+
# the specification and protocol copies, because these records share their
|
|
484
|
+
# release and registry folders.
|
|
485
|
+
def copy_decision_and_risk_images
|
|
486
|
+
root = @configuration.project_root_directory
|
|
487
|
+
%w[decisions risks].each do |subroot|
|
|
488
|
+
Dir.glob("#{root}/#{subroot}/**/img").each do |src|
|
|
489
|
+
next unless File.directory?(src)
|
|
490
|
+
|
|
491
|
+
dst = "#{root}/build/#{src.sub("#{root}/", '')}"
|
|
492
|
+
FileUtils.mkdir_p(File.dirname(dst))
|
|
493
|
+
FileUtils.copy_entry(src, dst)
|
|
494
|
+
end
|
|
495
|
+
end
|
|
496
|
+
end
|
|
497
|
+
|
|
588
498
|
def render_all_source_files
|
|
589
499
|
path = @configuration.project_root_directory
|
|
590
500
|
FileUtils.mkdir_p("#{path}/build/source_files")
|
|
@@ -611,16 +521,6 @@ class Project
|
|
|
611
521
|
doc.to_html("#{path}/build/decisions/")
|
|
612
522
|
end
|
|
613
523
|
|
|
614
|
-
def render_critical_chain_page
|
|
615
|
-
return if @project_data.decisions.empty?
|
|
616
|
-
|
|
617
|
-
path = @configuration.project_root_directory
|
|
618
|
-
FileUtils.mkdir_p("#{path}/build/decisions")
|
|
619
|
-
|
|
620
|
-
doc = DocFabric.create_critical_chain_page(@project)
|
|
621
|
-
doc.to_html("#{path}/build/decisions/")
|
|
622
|
-
end
|
|
623
|
-
|
|
624
524
|
def render_all_decisions
|
|
625
525
|
return if @project_data.decisions.empty?
|
|
626
526
|
|
|
@@ -1,11 +1,6 @@
|
|
|
1
1
|
require 'yaml'
|
|
2
|
-
require 'date'
|
|
3
2
|
|
|
4
3
|
class ProjectConfiguration
|
|
5
|
-
DEFAULT_WIP_LIMIT = 2
|
|
6
|
-
DEFAULT_BUFFER_RATIO = 0.5
|
|
7
|
-
DEFAULT_HOURS_PER_DAY = 8
|
|
8
|
-
|
|
9
4
|
attr_accessor :project_root_directory, :parameters
|
|
10
5
|
|
|
11
6
|
def initialize(path)
|
|
@@ -34,76 +29,11 @@ class ProjectConfiguration
|
|
|
34
29
|
[]
|
|
35
30
|
end
|
|
36
31
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
value = planning['wip_limit']
|
|
44
|
-
return DEFAULT_WIP_LIMIT unless value.is_a?(Integer) && value.positive?
|
|
45
|
-
|
|
46
|
-
value
|
|
47
|
-
end
|
|
48
|
-
|
|
49
|
-
# The CCPM project-buffer ratio (ADR-195): a fraction in (0, 1] cutting the
|
|
50
|
-
# aggregated chain safety. Absent or out-of-range falls back to the 0.5 default.
|
|
51
|
-
def get_buffer_ratio
|
|
52
|
-
return DEFAULT_BUFFER_RATIO unless @parameters.is_a?(Hash)
|
|
53
|
-
|
|
54
|
-
planning = @parameters['planning']
|
|
55
|
-
return DEFAULT_BUFFER_RATIO unless planning.is_a?(Hash)
|
|
56
|
-
|
|
57
|
-
value = planning['buffer_ratio']
|
|
58
|
-
return DEFAULT_BUFFER_RATIO unless value.is_a?(Numeric) && value.positive? && value <= 1
|
|
59
|
-
|
|
60
|
-
value
|
|
61
|
-
end
|
|
62
|
-
|
|
63
|
-
# The calendar anchor for working day 1 (ADR-205), a Date parsed from a
|
|
64
|
-
# DD-MM-YYYY planning.start_date. Absent or unparseable falls back to today,
|
|
65
|
-
# so an unconfigured project still renders (a moving anchor).
|
|
66
|
-
def get_start_date
|
|
67
|
-
date = parse_planning_date(planning_value('start_date'))
|
|
68
|
-
date || Date.today
|
|
69
|
-
end
|
|
70
|
-
|
|
71
|
-
# Per-group planning start dates (ADR-211): a map from a decision group's
|
|
72
|
-
# first-level folder name (under decisions/) to its start Date, read from
|
|
73
|
-
# planning.groups as DD-MM-YYYY entries. Non-string or unparseable values are
|
|
74
|
-
# dropped; empty when unset. A group absent from the map sequences after the
|
|
75
|
-
# previous group rather than carrying a declared start.
|
|
76
|
-
def get_group_start_dates
|
|
77
|
-
value = planning_value('groups')
|
|
78
|
-
return {} unless value.is_a?(Hash)
|
|
79
|
-
|
|
80
|
-
value.each_with_object({}) do |(name, raw), acc|
|
|
81
|
-
date = parse_planning_date(raw)
|
|
82
|
-
acc[name.to_s] = date if date
|
|
83
|
-
end
|
|
84
|
-
end
|
|
85
|
-
|
|
86
|
-
# The non-working holiday dates (ADR-205) from planning.holidays, each a
|
|
87
|
-
# DD-MM-YYYY entry; unparseable entries are dropped. Empty when unset.
|
|
88
|
-
def get_holidays
|
|
89
|
-
value = planning_value('holidays')
|
|
90
|
-
return [] unless value.is_a?(Array)
|
|
91
|
-
|
|
92
|
-
value.filter_map { |entry| parse_planning_date(entry) }
|
|
93
|
-
end
|
|
94
|
-
|
|
95
|
-
# Working hours per day (ADR-196): converts logged effort hours into the
|
|
96
|
-
# working-day unit the estimates use. Absent or non-positive falls back to 8.
|
|
97
|
-
def get_hours_per_day
|
|
98
|
-
return DEFAULT_HOURS_PER_DAY unless @parameters.is_a?(Hash)
|
|
99
|
-
|
|
100
|
-
planning = @parameters['planning']
|
|
101
|
-
return DEFAULT_HOURS_PER_DAY unless planning.is_a?(Hash)
|
|
102
|
-
|
|
103
|
-
value = planning['hours_per_day']
|
|
104
|
-
return DEFAULT_HOURS_PER_DAY unless value.is_a?(Numeric) && value.positive?
|
|
105
|
-
|
|
106
|
-
value
|
|
32
|
+
# The optional base text size in pixels (ADR-224), from the top-level
|
|
33
|
+
# font_size: number. nil when absent or not a positive number.
|
|
34
|
+
def get_font_size
|
|
35
|
+
value = @parameters.is_a?(Hash) ? @parameters['font_size'] : nil
|
|
36
|
+
value.is_a?(Numeric) && value.positive? ? value : nil
|
|
107
37
|
end
|
|
108
38
|
|
|
109
39
|
# The ordered register-column list for a risk registry folder (ADR-216),
|
|
@@ -164,26 +94,4 @@ class ProjectConfiguration
|
|
|
164
94
|
def numeric_threshold(value)
|
|
165
95
|
value.is_a?(Numeric) ? value : nil
|
|
166
96
|
end
|
|
167
|
-
|
|
168
|
-
# A value under the planning: key, or nil when planning is absent.
|
|
169
|
-
def planning_value(key)
|
|
170
|
-
return nil unless @parameters.is_a?(Hash)
|
|
171
|
-
|
|
172
|
-
planning = @parameters['planning']
|
|
173
|
-
planning.is_a?(Hash) ? planning[key] : nil
|
|
174
|
-
end
|
|
175
|
-
|
|
176
|
-
# Parse a planning date that may already be a Date (YAML ISO form) or a
|
|
177
|
-
# DD-MM-YYYY string; nil when neither.
|
|
178
|
-
def parse_planning_date(value)
|
|
179
|
-
return value if value.is_a?(Date)
|
|
180
|
-
return nil unless value.is_a?(String)
|
|
181
|
-
|
|
182
|
-
match = /\A(\d{2})-(\d{2})-(\d{4})\z/.match(value.strip)
|
|
183
|
-
return nil unless match
|
|
184
|
-
|
|
185
|
-
Date.new(match[3].to_i, match[2].to_i, match[1].to_i)
|
|
186
|
-
rescue ArgumentError
|
|
187
|
-
nil
|
|
188
|
-
end
|
|
189
97
|
end
|
|
@@ -5,7 +5,10 @@ html {
|
|
|
5
5
|
}
|
|
6
6
|
body {
|
|
7
7
|
font-family: Verdana, sans-serif;
|
|
8
|
-
|
|
8
|
+
/* Base text size, overridable per project via font_size in project.yml
|
|
9
|
+
(ADR-224). Headings, top menu and footer are pinned in px below so the
|
|
10
|
+
setting scales only the reading text and the navigation pane. */
|
|
11
|
+
font-size: var(--almirah-font-size, 12px);
|
|
9
12
|
color: #333;
|
|
10
13
|
margin: 0;
|
|
11
14
|
padding: 0;
|
|
@@ -30,24 +33,24 @@ body {
|
|
|
30
33
|
margin: 0 0 10px 0;
|
|
31
34
|
}
|
|
32
35
|
h1 {
|
|
33
|
-
font-size:
|
|
36
|
+
font-size: 24px;
|
|
34
37
|
}
|
|
35
38
|
h2 {
|
|
36
|
-
font-size:
|
|
39
|
+
font-size: 21.6px;
|
|
37
40
|
}
|
|
38
41
|
h3 {
|
|
39
|
-
font-size:
|
|
42
|
+
font-size: 18px;
|
|
40
43
|
}
|
|
41
44
|
h4 {
|
|
42
|
-
font-size:
|
|
45
|
+
font-size: 14.4px;
|
|
43
46
|
border: none;
|
|
44
47
|
font-weight: bold;
|
|
45
48
|
}
|
|
46
49
|
h5 {
|
|
47
|
-
font-size:
|
|
50
|
+
font-size: 12px;
|
|
48
51
|
}
|
|
49
52
|
h6 {
|
|
50
|
-
font-size:
|
|
53
|
+
font-size: 12px; color: #8e8e8e;
|
|
51
54
|
}
|
|
52
55
|
a.heading_anchor {
|
|
53
56
|
display: none;
|
|
@@ -147,163 +150,6 @@ table.controlled td.item_text{
|
|
|
147
150
|
max-width: 100%;
|
|
148
151
|
max-height: 300px;
|
|
149
152
|
}
|
|
150
|
-
.workitem_gantt {
|
|
151
|
-
--gantt-owner-width: 120px;
|
|
152
|
-
--gantt-day-width: 26px;
|
|
153
|
-
--gantt-month-h: 18px;
|
|
154
|
-
overflow: auto;
|
|
155
|
-
max-height: 420px;
|
|
156
|
-
margin-bottom: 16px;
|
|
157
|
-
border: 1px solid #e1e4e8;
|
|
158
|
-
}
|
|
159
|
-
.workitem_gantt .gantt_grid {
|
|
160
|
-
display: grid;
|
|
161
|
-
gap: 0;
|
|
162
|
-
width: max-content;
|
|
163
|
-
padding: 4px;
|
|
164
|
-
}
|
|
165
|
-
.workitem_gantt .gantt_corner,
|
|
166
|
-
.workitem_gantt .gantt_owner,
|
|
167
|
-
.workitem_gantt .gantt_owner_tracking,
|
|
168
|
-
.workitem_gantt .gantt_band_corner,
|
|
169
|
-
.workitem_gantt .gantt_buffer {
|
|
170
|
-
position: sticky;
|
|
171
|
-
left: 0;
|
|
172
|
-
background: #fff;
|
|
173
|
-
padding: 2px 8px;
|
|
174
|
-
font-weight: 600;
|
|
175
|
-
white-space: nowrap;
|
|
176
|
-
border-right: 1px solid #e1e4e8;
|
|
177
|
-
z-index: 2;
|
|
178
|
-
}
|
|
179
|
-
.workitem_gantt .gantt_corner {
|
|
180
|
-
top: 0;
|
|
181
|
-
border-bottom: 1px solid #bbb;
|
|
182
|
-
z-index: 3;
|
|
183
|
-
}
|
|
184
|
-
.workitem_gantt .gantt_month_head {
|
|
185
|
-
position: sticky;
|
|
186
|
-
top: 0;
|
|
187
|
-
background: #fff;
|
|
188
|
-
text-align: center;
|
|
189
|
-
font-size: 11px;
|
|
190
|
-
font-weight: 600;
|
|
191
|
-
color: #57606a;
|
|
192
|
-
border-bottom: 1px solid #e1e4e8;
|
|
193
|
-
border-left: 1px solid #d0d7de;
|
|
194
|
-
white-space: nowrap;
|
|
195
|
-
overflow: hidden;
|
|
196
|
-
z-index: 1;
|
|
197
|
-
}
|
|
198
|
-
.workitem_gantt .gantt_day_head {
|
|
199
|
-
position: sticky;
|
|
200
|
-
top: var(--gantt-month-h);
|
|
201
|
-
background: #fff;
|
|
202
|
-
text-align: center;
|
|
203
|
-
font-size: 11px;
|
|
204
|
-
color: #57606a;
|
|
205
|
-
border-bottom: 1px solid #bbb;
|
|
206
|
-
z-index: 1;
|
|
207
|
-
}
|
|
208
|
-
/* Weekday-holiday highlight in the Chart.js palette red (ADR-205 amendment):
|
|
209
|
-
the column carries the buffer bar's diagonal stripe texture, in red; the
|
|
210
|
-
header stays a solid tint so the day number reads. */
|
|
211
|
-
.workitem_gantt .gantt_day_head.gantt_nonworking { background: rgba(255, 99, 132, 0.22); color: #b23a52; }
|
|
212
|
-
.workitem_gantt .gantt_day_head.gantt_friday { background: rgb(225, 241, 250); }
|
|
213
|
-
.workitem_gantt .gantt_nonworking_col {
|
|
214
|
-
background: repeating-linear-gradient(45deg,
|
|
215
|
-
rgba(255, 99, 132, 0.20), rgba(255, 99, 132, 0.20) 5px,
|
|
216
|
-
rgba(255, 99, 132, 0.07) 5px, rgba(255, 99, 132, 0.07) 10px);
|
|
217
|
-
}
|
|
218
|
-
.workitem_gantt .gantt_friday_col { background: rgb(225, 241, 250); }
|
|
219
|
-
.workitem_gantt .gantt_bar,
|
|
220
|
-
.workitem_gantt .gantt_track_bar {
|
|
221
|
-
font-size: 11px;
|
|
222
|
-
line-height: 1.5;
|
|
223
|
-
margin: 1px;
|
|
224
|
-
padding: 1px 5px;
|
|
225
|
-
border-radius: 3px;
|
|
226
|
-
overflow: hidden;
|
|
227
|
-
white-space: nowrap;
|
|
228
|
-
text-overflow: ellipsis;
|
|
229
|
-
color: #fff;
|
|
230
|
-
cursor: default;
|
|
231
|
-
}
|
|
232
|
-
.workitem_gantt .gantt_todo { background: #9aa5b1; }
|
|
233
|
-
.workitem_gantt .gantt_inprogress { background: #ffffdd; color: #333; box-shadow: inset 0 0 0 1px #bbb; }
|
|
234
|
-
.workitem_gantt .gantt_done { background: #cfc; color: #57606a; box-shadow: inset 0 0 0 1px #e1e4e8; }
|
|
235
|
-
.workitem_gantt .gantt_blocked { box-shadow: inset 0 0 0 1px #bbb; }
|
|
236
|
-
.workitem_gantt .gantt_critical { outline: 1px solid #9a6700; outline-offset: -1px; } /* buffer-bar amber (dark) */
|
|
237
|
-
.workitem_gantt .gantt_release_band {
|
|
238
|
-
text-align: center;
|
|
239
|
-
font-size: 11px;
|
|
240
|
-
font-weight: 600;
|
|
241
|
-
color: #57606a;
|
|
242
|
-
background: #eef1f4;
|
|
243
|
-
border-bottom: 1px solid #e1e4e8;
|
|
244
|
-
border-left: 1px solid #d0d7de;
|
|
245
|
-
padding: 2px 4px;
|
|
246
|
-
white-space: nowrap;
|
|
247
|
-
overflow: hidden;
|
|
248
|
-
text-overflow: ellipsis;
|
|
249
|
-
}
|
|
250
|
-
.workitem_gantt .gantt_buffer { color: #9a6700; }
|
|
251
|
-
.critical_chain { margin-bottom: 16px; }
|
|
252
|
-
.critical_chain h2 { font-size: 1.1em; margin-bottom: 6px; }
|
|
253
|
-
.critical_chain .cc_group { margin: 8px 0; }
|
|
254
|
-
.critical_chain .cc_group h3 { font-size: 0.95em; margin: 4px 0; }
|
|
255
|
-
.critical_chain table.cc_chain { border-collapse: collapse; margin: 4px 0; }
|
|
256
|
-
.critical_chain table.cc_chain th,
|
|
257
|
-
.critical_chain table.cc_chain td { border: 1px solid #e1e4e8; padding: 2px 8px; text-align: left; font-size: 12px; }
|
|
258
|
-
.critical_chain .cc_buffer,
|
|
259
|
-
.critical_chain .cc_consumed,
|
|
260
|
-
.critical_chain .cc_projected,
|
|
261
|
-
.critical_chain .cc_finish { margin: 2px 0; font-size: 12px; }
|
|
262
|
-
.critical_chain .cc_consumed { color: #57606a; }
|
|
263
|
-
.critical_chain .cc_finish { font-weight: 600; }
|
|
264
|
-
.critical_chain .cc_unestimated { color: #9a6700; font-style: italic; font-size: 12px; }
|
|
265
|
-
.critical_chain .cc_group_body { display: flex; flex-wrap: wrap; gap: 24px; align-items: flex-start; }
|
|
266
|
-
.critical_chain .cc_plan { flex: 1 1 320px; }
|
|
267
|
-
.critical_chain .cc_fever { flex: 1 1 360px; max-width: 480px; height: 320px; position: relative; }
|
|
268
|
-
.critical_chain .cc_fever .fever_canvas { width: 100%; height: 100%; }
|
|
269
|
-
.workitem_gantt .gantt_buffer_bar {
|
|
270
|
-
font-size: 11px;
|
|
271
|
-
line-height: 1.5;
|
|
272
|
-
margin: 1px;
|
|
273
|
-
padding: 1px 5px;
|
|
274
|
-
border-radius: 3px;
|
|
275
|
-
text-align: center;
|
|
276
|
-
overflow: hidden;
|
|
277
|
-
white-space: nowrap;
|
|
278
|
-
text-overflow: ellipsis;
|
|
279
|
-
color: #9a6700;
|
|
280
|
-
background: repeating-linear-gradient(45deg, #ffe9b3, #ffe9b3 5px, #fff3d4 5px, #fff3d4 10px);
|
|
281
|
-
box-shadow: inset 0 0 0 1px #e0c068;
|
|
282
|
-
cursor: default;
|
|
283
|
-
}
|
|
284
|
-
.workitem_gantt .gantt_today { border-left: 2px solid #d33; z-index: 5; pointer-events: none; }
|
|
285
|
-
/* Plan-vs-actual tracking lanes (ADR-213): hidden until the toolbar toggle adds
|
|
286
|
-
show-actuals; a hidden lane's grid tracks collapse to zero height. */
|
|
287
|
-
.workitem_gantt .gantt_tracking_lane { display: none; }
|
|
288
|
-
.workitem_gantt.show-actuals .gantt_tracking_lane { display: block; }
|
|
289
|
-
.workitem_gantt .gantt_owner_tracking { font-weight: 400; font-style: italic; color: #57606a; }
|
|
290
|
-
.workitem_gantt .gantt_track_committed { background: #b0b6bd; }
|
|
291
|
-
.workitem_gantt .gantt_track_logged { background: #36a2eb; }
|
|
292
|
-
.gantt_container .gantt_toolbar {
|
|
293
|
-
display: flex;
|
|
294
|
-
justify-content: flex-end;
|
|
295
|
-
gap: 6px;
|
|
296
|
-
padding: 4px 0;
|
|
297
|
-
}
|
|
298
|
-
.gantt_container .gantt_actuals_toggle {
|
|
299
|
-
font-size: 12px;
|
|
300
|
-
padding: 3px 10px;
|
|
301
|
-
border: 1px solid #d0d7de;
|
|
302
|
-
border-radius: 4px;
|
|
303
|
-
background: #f6f8fa;
|
|
304
|
-
cursor: pointer;
|
|
305
|
-
}
|
|
306
|
-
.gantt_container .gantt_actuals_toggle:hover { background: #eef1f4; }
|
|
307
153
|
table.controlled:not(.odd-even) tbody tr:nth-child(odd) { background-color:#f6f7f8; }
|
|
308
154
|
table.controlled:not(.odd-even) tbody tr:nth-child(even) { background-color: #fff; }
|
|
309
155
|
table.controlled:not(.odd-even) tbody tr:nth-child(odd):hover,
|
|
@@ -417,7 +263,7 @@ img{
|
|
|
417
263
|
text-align: center;
|
|
418
264
|
padding: 4px 6px;
|
|
419
265
|
text-decoration: none;
|
|
420
|
-
font-size:
|
|
266
|
+
font-size: 18px;
|
|
421
267
|
font-family: "Trebuchet MS", Verdana, sans-serif;
|
|
422
268
|
}
|
|
423
269
|
#top_nav a.split {
|
|
@@ -426,7 +272,7 @@ img{
|
|
|
426
272
|
text-align: center;
|
|
427
273
|
padding: 4px 6px;
|
|
428
274
|
text-decoration: none;
|
|
429
|
-
font-size:
|
|
275
|
+
font-size: 18px;
|
|
430
276
|
font-family: "Trebuchet MS", Verdana, sans-serif;
|
|
431
277
|
}
|
|
432
278
|
#top_nav a:hover {
|
|
@@ -500,7 +346,7 @@ img{
|
|
|
500
346
|
#footer {
|
|
501
347
|
clear: both;
|
|
502
348
|
border-top: 1px solid #bbb;
|
|
503
|
-
font-size:
|
|
349
|
+
font-size: 10.8px;
|
|
504
350
|
color: #aaa;
|
|
505
351
|
padding: 5px;
|
|
506
352
|
text-align:center;
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: Almirah
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.4.
|
|
4
|
+
version: 0.4.6
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Oleksandr Ivanov
|
|
@@ -77,13 +77,10 @@ files:
|
|
|
77
77
|
- lib/almirah/doc_items/source_code_paragraph.rb
|
|
78
78
|
- lib/almirah/doc_items/text_line.rb
|
|
79
79
|
- lib/almirah/doc_items/todo_block.rb
|
|
80
|
-
- lib/almirah/doc_items/work_item.rb
|
|
81
80
|
- lib/almirah/doc_parser.rb
|
|
82
81
|
- lib/almirah/doc_types/base_document.rb
|
|
83
82
|
- lib/almirah/doc_types/coverage.rb
|
|
84
|
-
- lib/almirah/doc_types/critical_chain_page.rb
|
|
85
83
|
- lib/almirah/doc_types/decision.rb
|
|
86
|
-
- lib/almirah/doc_types/decision_grouping.rb
|
|
87
84
|
- lib/almirah/doc_types/decisions_overview.rb
|
|
88
85
|
- lib/almirah/doc_types/implementation.rb
|
|
89
86
|
- lib/almirah/doc_types/index.rb
|
|
@@ -103,12 +100,8 @@ files:
|
|
|
103
100
|
- lib/almirah/link_registry.rb
|
|
104
101
|
- lib/almirah/navigation_pane.rb
|
|
105
102
|
- lib/almirah/project.rb
|
|
106
|
-
- lib/almirah/project/critical_chain.rb
|
|
107
103
|
- lib/almirah/project/doc_linker.rb
|
|
108
|
-
- lib/almirah/project/fever_chart.rb
|
|
109
104
|
- lib/almirah/project/project_data.rb
|
|
110
|
-
- lib/almirah/project/work_item_scheduler.rb
|
|
111
|
-
- lib/almirah/project/working_calendar.rb
|
|
112
105
|
- lib/almirah/project_configuration.rb
|
|
113
106
|
- lib/almirah/project_template.rb
|
|
114
107
|
- lib/almirah/project_utility.rb
|