Merge lp:~jkakar/kanban/optional-needs-testing into lp:kanban

Proposed by Jamu Kakar
Status: Merged
Approved by: Martin Pool
Approved revision: 28
Merged at revision: 24
Proposed branch: lp:~jkakar/kanban/optional-needs-testing
Merge into: lp:kanban
Diff against target: 555 lines (+246/-39)
6 files modified
kanban/board.py (+28/-8)
kanban/commands.py (+14/-6)
kanban/templates/kanban.html (+88/-2)
kanban/tests/test_board.py (+35/-16)
media/decogrids-10.css (+74/-0)
media/decogrids-12.css (+7/-7)
To merge this branch: bzr merge lp:~jkakar/kanban/optional-needs-testing
Reviewer Review Type Date Requested Status
Martin Pool Approve
Review via email: mp+53119@code.launchpad.net

Description of the change

This branch introduces the following changes:

- The 'generate-milestone-kanban' and 'generate-person-kanban'
  commands both take an optional --include-needs-testing argument.
  The 'Needs testing' category is disabled by default, and only
  displayed when this argument is provided.

- A new 10-column grid CSS file is available, and is used when the
  'Needs testing' category is disabled.

To post a comment you must log in.
28. By Jamu Kakar

- Improved help text when 'Needs testing' is disabled.

Revision history for this message
Martin Pool (mbp) :
review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'kanban/board.py'
2--- kanban/board.py 2011-02-17 10:07:00 +0000
3+++ kanban/board.py 2011-03-12 20:16:43 +0000
4@@ -126,10 +126,16 @@
5
6
7 class BugCollectionMixin(object):
8- """A named collecton of L{Bug}s organized into categories."""
9-
10- def __init__(self, name):
11+ """A named collecton of L{Bug}s organized into categories.
12+
13+ @param name: The name of the L{Bug} collection.
14+ @param include_needs_testing: Optionally, a flag indicating whether or not
15+ to use the 'Needs testing' category. Defaults to C{False}.
16+ """
17+
18+ def __init__(self, name, include_needs_testing=False):
19 self.name = name
20+ self.include_needs_testing = include_needs_testing
21 self.bugs = []
22 self.queued = []
23 self.in_progress = []
24@@ -146,7 +152,10 @@
25 elif bug.needs_release():
26 self.needs_release.append(bug)
27 elif bug.needs_testing():
28- self.needs_testing.append(bug)
29+ if self.include_needs_testing:
30+ self.needs_testing.append(bug)
31+ else:
32+ self.needs_release.append(bug)
33 elif bug.needs_review():
34 self.needs_review.append(bug)
35 elif bug.in_progress():
36@@ -159,6 +168,8 @@
37 """A story is a collection of L{Bug}s related to a particular feature.
38
39 @param name: The name of this story.
40+ @param include_needs_testing: Optionally, a flag indicating whether or not
41+ to use the 'Needs testing' category. Defaults to C{False}.
42 """
43
44
45@@ -166,10 +177,13 @@
46 """A collection of L{Bug}s grouped into L{Story}s.
47
48 @param name: The name of this collection.
49+ @param include_needs_testing: Optionally, a flag indicating whether or not
50+ to use the 'Needs testing' category. Defaults to C{False}.
51 """
52
53- def __init__(self, name):
54- super(StoryCollectionMixin, self).__init__(name)
55+ def __init__(self, name, include_needs_testing=None):
56+ super(StoryCollectionMixin, self).__init__(
57+ name, include_needs_testing=include_needs_testing)
58 self._stories = {}
59
60 @property
61@@ -213,10 +227,14 @@
62 @param project_name: The name of the project or project group in Launchpad
63 this milestone is part of.
64 @param milestone_name: The name of this milestone.
65+ @param include_needs_testing: Optionally, a flag indicating whether or not
66+ to use the 'Needs testing' category. Defaults to C{False}.
67 """
68
69- def __init__(self, project_name, milestone_name):
70- super(MilestoneBoard, self).__init__(milestone_name)
71+ def __init__(self, project_name, milestone_name,
72+ include_needs_testing=None):
73+ super(MilestoneBoard, self).__init__(
74+ milestone_name, include_needs_testing=include_needs_testing)
75 self.project_name = project_name
76
77
78@@ -226,6 +244,8 @@
79 person or team.
80
81 @param name: The name of the person or team.
82+ @param include_needs_testing: Optionally, a flag indicating whether or not
83+ to use the 'Needs testing' category. Defaults to C{False}.
84 """
85
86
87
88=== modified file 'kanban/commands.py'
89--- kanban/commands.py 2011-02-17 10:07:00 +0000
90+++ kanban/commands.py 2011-03-12 20:16:43 +0000
91@@ -57,12 +57,15 @@
92
93 takes_args = ["person_name"]
94 takes_options = [Option("output-file", short_name="o", type=str,
95- help="Write HTML to file.")]
96+ help="Write HTML to file."),
97+ Option("include-needs-testing",
98+ help="Include the 'Needs testing' category.")]
99 _see_also = ["launchpad-login"]
100
101- def run(self, person_name, output_file=None):
102+ def run(self, person_name, output_file=None, include_needs_testing=None):
103 launchpad = get_launchpad()
104- person_board = PersonBoard(person_name)
105+ person_board = PersonBoard(person_name,
106+ include_needs_testing=include_needs_testing)
107 bugs = get_person_assigned_bugs(launchpad, person_name)
108 for bug in sorted(bugs, compare_bugs):
109 person_board.add(bug)
110@@ -74,12 +77,17 @@
111
112 takes_args = ["project_group", "milestone_name"]
113 takes_options = [Option("output-file", short_name="o", type=str,
114- help="Write HTML to file.")]
115+ help="Write HTML to file."),
116+ Option("include-needs-testing",
117+ help="Include the 'Needs testing' category.")]
118 _see_also = ["launchpad-login"]
119
120- def run(self, project_group, milestone_name, output_file=None):
121+ def run(self, project_group, milestone_name, output_file=None,
122+ include_needs_testing=None):
123 launchpad = get_launchpad()
124- milestone_board = MilestoneBoard(project_group, milestone_name)
125+ milestone_board = MilestoneBoard(
126+ project_group, milestone_name,
127+ include_needs_testing=include_needs_testing)
128 bugs = get_milestone_bugs(launchpad, project_group, milestone_name)
129 for bug in sorted(bugs, compare_bugs):
130 milestone_board.add(bug)
131
132=== modified file 'kanban/templates/kanban.html'
133--- kanban/templates/kanban.html 2011-02-17 10:07:00 +0000
134+++ kanban/templates/kanban.html 2011-03-12 20:16:43 +0000
135@@ -1,14 +1,22 @@
136 <html>
137 <head>
138 <title>{{ kanban_board.project_name }} {{ kanban_board.name }}</title>
139+ {% if kanban_board.include_needs_testing %}
140 <link rel="stylesheet" type="text/css" href="media/decogrids-12.css" />
141+ {% else %}
142+ <link rel="stylesheet" type="text/css" href="media/decogrids-10.css" />
143+ {% endif %}
144 <link rel="stylesheet" type="text/css" href="media/kanban.css" />
145 </head>
146
147 <body>
148 <div id="container">
149 <div id="heading" class="row">
150+ {% if kanban_board.include_needs_testing %}
151 <div class="position-0 width-12 cell">
152+ {% else %}
153+ <div class="position-0 width-10 cell">
154+ {% endif %}
155 {% if is_milestone %}
156 <h1><a href="https://launchpad.net/{{ kanban_board.project_name }}">{{ kanban_board.project_name }}</a> <a href="https://launchpad.net/{{ kanban_board.project_name }}/+milestone/{{ kanban_board.name}}">{{ kanban_board.name }}</a> <span class="bug-count">{{ kanban_board.bugs|length }} bugs</span></h1>
157 {% else %}
158@@ -21,17 +29,30 @@
159 <div class="position-0 width-2 cell"><h2>Queued <span class="bug-count">{{ kanban_board.queued|length }} bugs</span></h2></div>
160 <div class="position-2 width-2 cell"><h2>In progress <span class="bug-count">{{ kanban_board.in_progress|length }} bugs</span></h2></div>
161 <div class="position-4 width-2 cell"><h2>Needs review <span class="bug-count">{{ kanban_board.needs_review|length }} bugs</span></h2></div>
162+ {% if kanban_board.include_needs_testing %}
163 <div class="position-6 width-2 cell"><h2>Needs testing <span class="bug-count">{{ kanban_board.needs_testing|length }} bugs</span></h2></div>
164 <div class="position-8 width-2 cell"><h2>Needs release <span class="bug-count">{{ kanban_board.needs_release|length }} bugs</span></h2></div>
165 <div class="position-10 width-2 cell"><h2>Released <span class="bug-count">{{ kanban_board.released|length }} bugs</span></h2></div>
166+ {% else %}
167+ <div class="position-6 width-2 cell"><h2>Needs release <span class="bug-count">{{ kanban_board.needs_release|length }} bugs</span></h2></div>
168+ <div class="position-8 width-2 cell"><h2>Released <span class="bug-count">{{ kanban_board.released|length }} bugs</span></h2></div>
169+ {% endif %}
170 </div>
171
172 {% for story in kanban_board.stories %}
173 <div class="tiles row story">
174 {% if story.name %}
175- <div class="position-0 width-12 cell">{{ story.name }}</div>
176+ {% if kanban_board.include_needs_testing %}
177+ <div class="position-0 width-12 cell">{{ story.name }}</div>
178+ {% else %}
179+ <div class="position-0 width-10 cell">{{ story.name }}</div>
180+ {% endif %}
181 {% else %}
182- <div class="position-0 width-12 cell">uncategorized</div>
183+ {% if kanban_board.include_needs_testing %}
184+ <div class="position-0 width-12 cell">uncategorized</div>
185+ {% else %}
186+ <div class="position-0 width-10 cell">uncategorized</div>
187+ {% endif %}
188 {% endif %}
189 </div>
190
191@@ -115,6 +136,7 @@
192
193 </div>
194
195+ {% if kanban_board.include_needs_testing %}
196 <div class="position-6 width-2 cell">
197
198 {% for bug in story.needs_testing %}
199@@ -171,6 +193,45 @@
200 {% endfor %}
201
202 </div>
203+ {% else %}
204+ <div class="position-6 width-2 cell">
205+
206+ {% for bug in story.needs_release %}
207+ <div class="tile">
208+ {% if bug.assignee %}
209+ <span class="assignee">{{ bug.assignee }}</span>
210+ {% endif %}
211+ <a href="https://bugs.launchpad.net/bugs/{{ bug.id }}">#{{ bug.id }}</a> <span class="{{ bug.importance|importance_css_class }}">{{ bug.importance }}</span>
212+ <br/>
213+ <div class="project">{{ bug.project }}</div>
214+ <div>{{ bug.title }}</div>
215+ {% if bug.branch %}
216+ <div class="merge-proposal"><a href="{{ bug|branch_url }}">{{ bug.branch|branch_name }}</a></div>
217+ {% endif %}
218+ </div>
219+ {% endfor %}
220+
221+ </div>
222+
223+ <div class="position-8 width-2 cell">
224+
225+ {% for bug in story.released %}
226+ <div class="tile">
227+ {% if bug.assignee %}
228+ <span class="assignee">{{ bug.assignee }}</span>
229+ {% endif %}
230+ <a href="https://bugs.launchpad.net/bugs/{{ bug.id }}">#{{ bug.id }}</a> <span class="{{ bug.importance|importance_css_class }}">{{ bug.importance }}</span>
231+ <br/>
232+ <div class="project">{{ bug.project }}</div>
233+ <div>{{ bug.title }}</div>
234+ {% if bug.branch %}
235+ <div class="merge-proposal"><a href="{{ bug|branch_url }}">{{ bug.branch|branch_name }}</a></div>
236+ {% endif %}
237+ </div>
238+ {% endfor %}
239+
240+ </div>
241+ {% endif %}
242 </div>
243 {% endfor %}
244
245@@ -195,6 +256,7 @@
246 </div>
247 </div>
248
249+ {% if kanban_board.include_needs_testing %}
250 <div class="position-6 width-2 cell">
251 <div class="legend-description">
252 <strong>Needs testing</strong> bugs are <em>Fix Committed</em>
253@@ -215,16 +277,40 @@
254 <strong>Fix released</strong> bugs are <em>Fix Released</em>.
255 </div>
256 </div>
257+ {% else %}
258+ <div class="position-6 width-2 cell">
259+ <div class="legend-description">
260+ <strong>Needs release</strong> bugs are <em>Fix
261+ Committed</em> or <em>In Progress</em> with
262+ an <em>Approved</em> or
263+ <em>Merged</em> merge proposal.
264+ </div>
265+ </div>
266+
267+ <div class="position-8 width-2 cell">
268+ <div class="legend-description">
269+ <strong>Fix released</strong> bugs are <em>Fix Released</em>.
270+ </div>
271+ </div>
272+ {% endif %}
273 </div>
274
275 <!-- XXX This is a hack. It'd be better to add bottom-rounded
276 borders to the last .tile element. -->
277 <div id="prefooter" class="row">
278+ {% if kanban_board.include_needs_testing %}
279 <div class="position-0 width-12 cell">&nbsp;</div>
280+ {% else %}
281+ <div class="position-0 width-10 cell">&nbsp;</div>
282+ {% endif %}
283 </div>
284
285 <div id="footer" class="row">
286+ {% if kanban_board.include_needs_testing %}
287 <div class="position-0 width-12 cell">Generated on {{ now }}.</div>
288+ {% else %}
289+ <div class="position-0 width-10 cell">Generated on {{ now }}.</div>
290+ {% endif %}
291 </div>
292
293 </div>
294
295=== modified file 'kanban/tests/test_board.py'
296--- kanban/tests/test_board.py 2011-02-17 10:07:00 +0000
297+++ kanban/tests/test_board.py 2011-03-12 20:16:43 +0000
298@@ -278,8 +278,8 @@
299 def test_add_queued(self):
300 """
301 A L{Bug} in the 'Queued' category is stored in the
302- L{BugCollectionMixin.bugs} and the L{Story.queued} list in the default
303- story.
304+ L{BugCollectionMixin.bugs} and the L{Story.queued} lists, in the
305+ default story.
306 """
307 bug = Bug("1", "kanban", MEDIUM, NEW, "A title")
308 kanban_board = self.create_test_class()
309@@ -295,7 +295,7 @@
310 def test_add_in_progress(self):
311 """
312 A L{Bug} in the 'In progress' category is stored in the
313- L{BugCollectionMixin.bugs} and the L{Story.in_progress} list in the
314+ L{BugCollectionMixin.bugs} and the L{Story.in_progress} lists, in the
315 default story.
316 """
317 bug = Bug("1", "kanban", MEDIUM, IN_PROGRESS, "A title")
318@@ -312,7 +312,7 @@
319 def test_add_needs_review(self):
320 """
321 A L{Bug} in the 'Needs review' category is stored in the
322- L{BugCollectionMixin.bugs} and the L{Story.in_progress} list in the
323+ L{BugCollectionMixin.bugs} and the L{Story.in_progress} lists, in the
324 default story.
325 """
326 bug = Bug("1", "kanban", MEDIUM, IN_PROGRESS, "A title",
327@@ -330,12 +330,12 @@
328 def test_add_needs_testing(self):
329 """
330 A L{Bug} in the 'Needs testing' category is stored in the
331- L{BugCollectionMixin.bugs} and the L{Story.needs_testing} list in the
332- default story.
333+ L{BugCollectionMixin.bugs} and the L{Story.needs_testing} lists, in
334+ the default story.
335 """
336 bug = Bug("1", "kanban", MEDIUM, IN_PROGRESS, "A title",
337 merge_proposal="url", merge_proposal_status=APPROVED)
338- kanban_board = self.create_test_class()
339+ kanban_board = self.create_test_class(include_needs_testing=True)
340 kanban_board.add(bug)
341 self.assertEqual([bug], kanban_board.bugs)
342 self.assertEqual([], kanban_board.queued)
343@@ -345,11 +345,29 @@
344 self.assertEqual([], kanban_board.needs_release)
345 self.assertEqual([], kanban_board.released)
346
347+ def test_add_needs_testing_disabled(self):
348+ """
349+ A L{Bug} in the 'Needs testing' category is stored in the
350+ L{BugCollectionMixin.bugs} and the L{Story.needs_release} lists, in
351+ the default story, when the 'Needs testing' category is disabled.
352+ """
353+ bug = Bug("1", "kanban", MEDIUM, IN_PROGRESS, "A title",
354+ merge_proposal="url", merge_proposal_status=APPROVED)
355+ kanban_board = self.create_test_class()
356+ kanban_board.add(bug)
357+ self.assertEqual([bug], kanban_board.bugs)
358+ self.assertEqual([], kanban_board.queued)
359+ self.assertEqual([], kanban_board.in_progress)
360+ self.assertEqual([], kanban_board.needs_review)
361+ self.assertEqual([], kanban_board.needs_testing)
362+ self.assertEqual([bug], kanban_board.needs_release)
363+ self.assertEqual([], kanban_board.released)
364+
365 def test_add_needs_release(self):
366 """
367 A L{Bug} in the 'Needs release' category is stored in the
368- L{BugCollectionMixin.bugs} and the L{Story.needs_release} list in the
369- default story.
370+ L{BugCollectionMixin.bugs} and the L{Story.needs_release} lists, in
371+ the default story.
372 """
373 bug = Bug("1", "kanban", MEDIUM, FIX_COMMITTED, "A title",
374 merge_proposal="url", merge_proposal_status=MERGED,
375@@ -367,7 +385,7 @@
376 def test_add_released(self):
377 """
378 A L{Bug} in the 'Released' category is stored in the
379- L{BugCollectionMixin.bugs} and the L{Story.released} list in the
380+ L{BugCollectionMixin.bugs} and the L{Story.released} lists, in the
381 default story.
382 """
383 bug = Bug("1", "kanban", MEDIUM, FIX_RELEASED, "A title")
384@@ -463,12 +481,13 @@
385 class MilestoneBoardTest(BugCollectionMixinTestBase,
386 StoryCollectionMixinTestBase, TestCase):
387
388- def create_test_class(self):
389+ def create_test_class(self, include_needs_testing=None):
390 """
391 Create a L{MilestoneBoard} for use in L{BugCollectionMixinTestBase}
392 and L{StoryCollectionMixinTestBase} tests.
393 """
394- return MilestoneBoard("project", "milestone")
395+ return MilestoneBoard("project", "milestone",
396+ include_needs_testing=include_needs_testing)
397
398 def test_instantiate(self):
399 """
400@@ -484,12 +503,12 @@
401 class PersonBoardTest(BugCollectionMixinTestBase, StoryCollectionMixinTestBase,
402 TestCase):
403
404- def create_test_class(self):
405+ def create_test_class(self, include_needs_testing=None):
406 """
407 Create a L{PersonBoard} for use in L{BugCollectionMixinTestBase} and
408 L{StoryCollectionMixinTestBase} tests.
409 """
410- return PersonBoard("team")
411+ return PersonBoard("team", include_needs_testing=include_needs_testing)
412
413 def test_instantiate(self):
414 """
415@@ -503,11 +522,11 @@
416
417 class StoryTest(BugCollectionMixinTestBase, TestCase):
418
419- def create_test_class(self):
420+ def create_test_class(self, include_needs_testing=None):
421 """
422 Create a L{Story} for use in L{BugCollectionMixinTestBase} tests.
423 """
424- return Story("name")
425+ return Story("name", include_needs_testing=include_needs_testing)
426
427
428 class CompareStoriesTest(TestCase):
429
430=== added file 'media/decogrids-10.css'
431--- media/decogrids-10.css 1970-01-01 00:00:00 +0000
432+++ media/decogrids-10.css 2011-03-12 20:16:43 +0000
433@@ -0,0 +1,74 @@
434+/* The 12-column Deco Grid System.
435+ * Available in multiple variants, see http://deco.gs
436+ *
437+ * Cells are 7.91668% (=76px)
438+ * Margins are 1.04166% (=10px) (times two, left and right)
439+ * Total is 10% (=96px) (which makes 10 columns)
440+ *
441+ * If page width is fixed to 960px width:
442+ * Cell will be equivalent to 76px, left/right margin will be 10px.
443+ *
444+ */
445+
446+div.row {
447+ float: left;
448+ width: 100%;
449+ display: block;
450+ position: relative;
451+}
452+div.cell {
453+ position: relative;
454+ float: left;
455+ left: 100%;
456+}
457+
458+/* Width classes.
459+ For a given cell width, the calculation is:
460+ width = (total cell width) * n - (margin*2)
461+ In this case: (8.333*n - 2.08333)% */
462+div.width-1 { width: 7.9166% }
463+div.width-2 { width: 17.916% }
464+div.width-3 { width: 27.916% }
465+div.width-4 { width: 37.916% }
466+div.width-5 { width: 47.916% }
467+div.width-6 { width: 57.916% }
468+div.width-7 { width: 67.916% }
469+div.width-8 { width: 77.916% }
470+div.width-9 { width: 87.916% }
471+div.width-10 { width: 97.916% }
472+
473+
474+
475+/* Positioning classes, these are subtracting from a rightmost
476+ position, which is why they seem the wrong way around */
477+/* For a given position, the calculation is:
478+ -100 + (total cell width * n)
479+ In this case: margin-left: -100 + (8.333*n) */
480+div.position-0 { margin-left: -100% }
481+div.position-1 { margin-left: -90% }
482+div.position-2 { margin-left: -80% }
483+div.position-3 { margin-left: -70% }
484+div.position-4 { margin-left: -60% }
485+div.position-5 { margin-left: -50% }
486+div.position-6 { margin-left: -40% }
487+div.position-7 { margin-left: -30% }
488+div.position-8 { margin-left: -20% }
489+div.position-9 { margin-left: -10% }
490+
491+/* End of the core Deco Grid System */
492+
493+/* Convenience classes — ¼, ½, ¾ widths and ¼, ½, ¾ positions.
494+ Not strictly necessary. */
495+div.width-1\3a 2 { width: 47.916%; } /* .width-1:2 */
496+div.width-1\3a 4 { width: 22.916%; } /* .width-1:4 */
497+div.width-3\3a 4 { width: 72.916%; } /* .width-3:4 */
498+div.position-1\3a 4 { margin-left: -75.001%;} /* .position-1:4 */
499+div.position-1\3a 2 { margin-left: -50.002%;} /* .position-1:2 */
500+div.position-3\3a 4 { margin-left: -25.003%;} /* .position-3:4 */
501+
502+
503+/* Special classes for ⅓, ⅔ widths and ⅓, ⅔ positions. */
504+div.width-1\3a 3 { width: 31.250%; } /* .width-1:3 */
505+div.width-2\3a 3 { width: 64.583%; } /* .width-2:3 */
506+div.position-1\3a 3 {margin-left: -66.668%;} /* .position-1:3 */
507+div.position-2\3a 3 {margin-left: -33.336%;} /* .position-2:3 */
508
509=== modified file 'media/decogrids-12.css'
510--- media/decogrids-12.css 2010-11-15 00:00:59 +0000
511+++ media/decogrids-12.css 2011-03-12 20:16:43 +0000
512@@ -1,10 +1,10 @@
513-/* The 12-column Deco Grid System.
514+/* The 12-column Deco Grid System.
515 * Available in multiple variants, see http://deco.gs
516 *
517 * Cells are 6.25% (=60px)
518 * Margins are 1.04166% (=10px) (times two, left and right)
519 * Total is 8.33333%. (=80px) (which makes 12 columns)
520- *
521+ *
522 * If page width is fixed to 960px width:
523 * Cell will be equivalent to 60px, left/right margin will be 10px.
524 *
525@@ -22,9 +22,9 @@
526 left: 100%;
527 }
528
529-/* Width classes.
530- For a given cell width, the calculation is:
531- width = (total cell width) * n - (margin*2)
532+/* Width classes.
533+ For a given cell width, the calculation is:
534+ width = (total cell width) * n - (margin*2)
535 In this case: (8.333*n - 2.08333)% */
536 div.width-1 { width: 6.2500% }
537 div.width-2 { width: 14.583% }
538@@ -39,7 +39,7 @@
539 div.width-11 { width: 89.583% }
540 div.width-12 { width: 97.916% }
541
542-/* Positioning classes, these are subtracting from a rightmost
543+/* Positioning classes, these are subtracting from a rightmost
544 position, which is why they seem the wrong way around */
545 /* For a given position, the calculation is:
546 -100 + (total cell width * n)
547@@ -59,7 +59,7 @@
548
549 /* End of the core Deco Grid System */
550
551-/* Convenience classes — ¼, ½, ¾ widths and ¼, ½, ¾ positions.
552+/* Convenience classes — ¼, ½, ¾ widths and ¼, ½, ¾ positions.
553 Not strictly necessary. */
554 div.width-1\3a 2 { width: 47.916%; } /* .width-1:2 */
555 div.width-1\3a 4 { width: 22.916%; } /* .width-1:4 */

Subscribers

People subscribed via source and target branches

to all changes: