Update level_about.coffee

Update level_about.coffee

--- server/materials/data-en/level_about.coffee
+++ server/materials/data-en/level_about.coffee
@@ -173,90 +173,90 @@

Sending for testing

Go back to the page with the problem statement. Below the statement there is a section "Submit a solution" (it is only there if you are registered on the site and logged in). Click the file selection button and select the file with your solution. Make sure you select the file .py or .cpp (with source code), not .pyc, .o or .exe (already compiled program)! Make sure that the correct language is selected in the drop-down list next to it, and click the "Send" button.

-
Your program is sent to the server, which checks it by running on several test examples. These test cases are unknown to you, but they are always the same in each specific problem (i.e. if you send several times, the test cases will be the same each time). In this case, the testing system will run your program several times, each time it will simulate keyboard input of the values N and K from the next test example, and check that your program outputs the correct answer.

-
-
Under the submission form there is a table listing all your attempts (also called "submissions") for this task. Initially, of course, there is no table there. After you have submitted your solution, it will appear in this table (after a while — about a minute). The "Status" column displays the result of testing your program. Immediately after you have submitted the program, you can see there "Compiling" or "Testing", which means that your task is being tested. The table is updated automatically, wait until the final result of the check appears in the "Status" column.

+
Your program is sent to the server, which checks it by running on several test cases. These test cases are unknown to you, but they are always the same in each specific problem (i.e. if you send several times, the test cases will be the same each time). So, the testing system will run your program several times, each time it will simulate keyboard input of the values N and K from the next test cases, and check that your program outputs the correct answer.

+
+
Under the submission form there is a table listing all your attempts (also called "submissions" or "submits") for this task. Initially, of course, there is no table there. After you have submitted your solution, it will appear in this table. The "Status" column displays the result of testing your program. Immediately after you have submitted the program, you can see there "Compiling" or "Testing", which means that your task is being tested. The table is updated automatically, wait until the final result of the check appears in the "Status" column.

The main results of the check that you can see:

OK — your program has passed all the tests correctly, hooray!

-
Compilation error — your program has not compiled

-
Partial solution — your program didn't pass all the tests (maybe it didn't even pass any). So, your program is not working quite right (or completely wrong), try to find errors.

-
There are also "Accepted" and "Ignored" statuses, but they are not set automatically, so about them below.

-

-
-
You can click on each line in the package table, after which a window with detailed information about the package will open. Firstly, there will be the full code of your program, so you can always check whether you have sent exactly what you wanted; if you solved problems from school, then when you come home, you will be able to see the codes of all your programs, etc.

-
-
Secondly, in the window that opens there is a tab "Protocol", where the test protocol of your program is displayed. If your program has not compiled (the status is "Compilation error"), then there will be a compilation protocol: errors found in the program will be indicated. If your program has compiled, then there will be a test protocol: for each test, information will be provided about whether this test passed (OK) or not. There are only 6 tests in the task "Dividing apples - 1", so there should be 6 rows in the table. If your submission has received the status "OK", then all 6 lines will be OK; otherwise, some tests will have a different status (it is usually clear from the name of the status what this means).

-
-
Thus, you can see which (by numbers) tests you have passed and which have not. But you can't find out which test it is (in our problem, which N and K are exactly there). This is done so that you learn to test your solution yourself and find examples on which it does not work, and do not consider the testing system as a magic box that gives you examples on which your program does not work. Therefore, if your program does not work, then look for examples on which it is wrong. If you can't find them, then write to me, we'll try to figure it out together. As a last resort, I have access to all the test data, so if I can't find an error just by looking at your program's code, I can see what kind of test your program doesn't work on.

-
-
But in general, try to check the task before sending it to the system (and we will learn this separately). Treat failed attempts as a specific failure, and try to make as few failed attempts as possible. In general, at many contests you will have only one opportunity to pass the task, and if it does not pass some tests, then you will no longer have the opportunity to improve!

-
-
Counting and ignoring decisions, comments on the decision

-
I will review all your decisions: if there is time, all submissions in general, including unsuccessful ones, if I have little time, and there are a lot of submissions, then only submissions with the OK status.

+
Compilation error — your program was not compiled

+
Partial solution — your program didn't pass all the tests (maybe it didn't even pass any). So, your program is not working quite right (or works completely wrong), try to find mistakes.

+
There are also "Accepted" and "Ignored" statuses, but they are not set automatically, see about them below.

+

+
+
You can click on each line in the submission table, after which a window with detailed information about the submission will open. Firstly, there will be the full code of your program, so you can always check whether you have sent exactly what you wanted; if you had been solving problems from school, then when you come home, you will be able to see the codes of all your programs, etc.

+
+
Secondly, in the window that opens there is a tab "Protocol", where the test protocol of your program is displayed. If your program was not compiled (the status is "Compilation error"), then there will be a compilation protocol: errors found in the program will be indicated. If your program was compiled, then there will be a test protocol: for each test, information will be provided about whether this test passed (OK) or not. There are only 8 tests in the task "Pears", so there should be 8 rows in the table. If your submission has received the status "OK", then all 8 lines will be OK; otherwise, some tests will have a different status (it is usually clear from the name of the status what this means).

+
+
Thus, you can see which (by numbers) tests you have passed and which have not. But you can't find out the contents of the test (in our problem, which N and K are exactly there). This is done so that you learn to test your solution yourself and find examples on which it does not work, and not consider the testing system as a magic box that gives you examples on which your program does not work. Therefore, if your program does not work, then try to find the examples on which it is wrong. If you can't find them, then contact me, we'll try to figure it out together. As a last resort, I have access to all the test data, so if I can't find an error just by looking at your program's code, I can see what kind of test your program doesn't work on.

+
+
But in general, try to check the task before sending it to the system (and we will learn this separately). Treat failed attempts as a real failure, and try to make as few failed attempts as possible. In general, at many programming contests you will have only one opportunity to submit the task, and if it does not pass some tests, then you will no longer have the opportunity to improve!

+
+
Accepting and ignoring submits, comments on submits

+
I will review all your decisions: if I have time, all submissions in general, including unsuccessful ones, or if I have little time, and there are a lot of submissions, then only submissions with the OK status.

In successful submissions, I will review your code for the following:

-
First, I will look to see if there are any errors in the code that the tests of the testing system were able to pass. It happens that there is some completely unexpected error in the code, and the testing system may miss it (although the tests in the testing system are usually well prepared, and as a rule all errors in the code manifest themselves during testing) - I will see if you have such errors.

+
First, I will look to see if there are any errors in the code that the tests of the testing system did not catch. It happens that there is some completely unexpected error in the code, and the testing system may miss it (although the tests in the testing system are usually well prepared, and as a rule all errors in the code manifest themselves during testing) - I will see if you have such errors.

I will also look to see if I have any comments on the code, even if they are not errors, but are general wishes. For example, maybe something can be done easier, or something should be done differently, etc.

-
If I have any comments or comments on your program, I will write them and you will be able to see them. Firstly, they will appear on the site in the right column in the "Comments" section; secondly, in each submission, in the window opened by the "more details" link, there is a special "comments" tab. Accordingly, as soon as I comment on your package, you will see a comment. Usually I try to view all the submissions during the day, although sometimes there are long delays, up to several days.

-
-
In addition to actually writing a comment, I make a decision on each of your submissions — either I make this decision (albeit with small comments), or I will require you to rewrite this decision taking into account my comments. In the first case, I change the status of the submission to "Accepted" (i.e. you will see exactly "Accepted" instead of "OK" in the table with your submissions), in the second case I change the status to "Ignored". Accordingly, in the first case, just take note of the comments that I wrote (and it happens that I do not write any comments), although you can rewrite the solution if you want. In the second case, be sure to rewrite the decision taking into account these comments.

-
(Read more about why I ignore decisions.)

-
-
In the summary table, the "OK" solutions (i.e., which I haven't looked at yet) are displayed with a yellow background, the "Counted" solutions (i.e., which I have accepted) are displayed with a green background, ignored solutions are not taken into account in the total number of tasks you have solved, just the corresponding task in the summary table is displayed with a blue background.

+
If I have any comments or comments on your program, I will write them and you will be able to see them. Firstly, they will appear on the site in the right column in the "Comments" section; secondly, in each submission, in the window opened by clicking the submit link, there is a special "comments" tab; finally, there is a separate Comments section in the lefthand menu. Accordingly, as soon as I comment on your package, you will see a comment. Usually I try to view all the submissions during the day, although sometimes there are long delays, up to several days.

+
+
In addition to actually writing a comment, I make a decision on each of your submissions — either I accept this submit (albeit with small comments), or I will require you to rewrite this submit taking into account my comments. In the first case, I change the status of the submission to "Accepted" (i.e. you will see exactly "Accepted" instead of "OK" in the table with your submissions), in the second case I change the status to "Ignored". Accordingly, in the first case, just take note of the comments that I wrote (and it happens that I do not write any comments), although you can rewrite the solution if you want. In the second case, be sure to rewrite the solution taking into account these comments.

+
(Read more about why I ignore decisions (in Russian).)

+
+
In the summary table, the "OK" solutions (i.e., which I haven't looked at yet) are displayed with a yellow background, the "Accepted" solutions (i.e., which I have accepted) are displayed with a green background, ignored solutions are not taken into account in the total number of tasks you have solved, just the corresponding task in the summary table is displayed with a blue background.

I will also try to write comments on your unsuccessful submissions — to point out an error or advise where to look for it, etc. Of course, I will not always write them as soon as I see your attempt, and I will not always fully report the error found — after all, it will usually be useful for you to find the error more-less independently, I will try to give you suggestive advice (but, of course, it depends on the error). In general, if you tried to do a task, but it does not pass the tests, then perhaps within a day I will write you a tip in the comments to your submission.

-
Sometimes I may not have time to review all your unsuccessful submissions, or I do not write a comment for some other reasons. In that case, if you want to get a comment on any of them, just email me.

+
Sometimes I may not have time to review all your unsuccessful submissions, or I do not write a comment for some other reasons. In that case, if you want to get a comment on any of them, just contact me.

Good solutions

After I accept your solution (I set the status "Accepted"), you have access to "good solutions" for the corresponding task — on the task page, above the form for submitting the solution, a link to "good solutions" appears.

-
"Good solutions" are several (up to 5 pieces) solutions that were passed by other students of the course, and which, in my opinion, are written quite well and can be considered an example of solving this problem. Watch them, especially for those tasks where I have accepted with any comments (but in general it is useful to watch "good solutions" and for all accepted tasks in general). Compare the "good solutions" with your solution, perhaps you will see that something can be done easier, or even find other useful ideas.

+
"Good solutions" are several (up to 5) solutions that were submitted by other students of the course, and which, in my opinion, are written quite well and can be considered an example of solving this problem. Watch them, especially for those tasks where I have accepted with any comments (but in general it is useful to watch "good solutions" and for all accepted tasks in general). Compare the "good solutions" with your solution, perhaps you will see that something can be done easier, or even find other useful ideas.

I try to keep the balance of different languages in the "good solutions" as much as possible (i.e., as a rule, there are solutions in different languages among the "good solutions"). But look at the solutions in your language, and in other languages that you don't even know — as a rule, you can understand the algorithm even in languages that you don't know at all. On the other hand, you can also find some interesting purely linguistic subtleties and techniques for your language in "good solutions".

In addition, often in "good solutions" I show different approaches to solving the problem, often there are generally different algorithms. If you see that a "good solution" is very different from yours, it is useful to understand how it works in general.

-
At the same time, understand that in fact, hundreds of solutions have already been submitted for many tasks on the site, and in fact there are dozens of "good" ones, if not more. You see a maximum of five of them, simply because there is no point in watching dozens of more or less identical solutions. You will see some five of them who were lucky that I marked them as "good", but this "luck" is often determined by chance, well, the principles mentioned above about the diversity of languages and approaches. That is, you don't see five of the best solutions, but five random ones from among the good ones.

-
-
Therefore, do not assume that if your personal decision did not get into the "good" ones, then it is worse than the "good" ones. It can be just as good, or even something better than the "good" ones, it's just unlucky. If you see that the "good" solutions are written as cleanly and clearly as yours, then yours could also be "good". Conversely, if your solution is in the "good", it does not mean that it is much better than all the others :) For the same reason, the authors of "good solutions" are not signed.

+
At the same time, understand that in fact, hundreds of solutions have already been submitted for many tasks on the site, and in fact there are dozens of "good" ones, if not more. You see a maximum of five of them, simply because there is no point in reading dozens of more or less identical solutions. You will see some five of them which were lucky that I marked them as "good", but this "luck" is often determined by chance, well, and the principles mentioned above about the diversity of languages and approaches. That is, you don't see the top-five best solutions, but five random ones from among those that are sufficiently good.

+
+
Therefore, do not assume that if your personal submit did not get into the "good" ones, then it is worse than the "good" ones. It can be just as good, or even something better than the "good" ones, it's just unlucky. If you see that the "good" solutions are written as cleanly and clearly as yours, then yours could also be "good". Conversely, if your solution is in the "good", it does not mean that it is much better than all the others :) For the same reason, the authors of "good solutions" are not signed.

On the other hand, if you have looked at the "good solutions" and think that your solution is noticeably better than all the "good" ones, write to me, maybe I will add your solution to the "good" ones. (Although I usually designate a solution as "good" at the same time as I count it, so if your solution is really noticeably better than other good ones, as a rule, I will make it "good" even before you can see "good solutions" :) .)

-
Summary table

-
All your submissions for our tasks are displayed in summary tables, links to which are in the site menu. Namely, each cell in the main part of this table indicates the statistics of submissions from a particular student for a specific task. If there is a "+" sign in the cell, it means that the task was passed successfully ("OK" or "Accepted"), if "-" — it means that the person tried to pass the task, but did not pass all the tests. The number after the icon (if any) indicates the number of unsuccessful attempts (if any). For example:
+
Rankings tables

+
All your submissions for our tasks are displayed in rankings tables, links to which are in the site menu. Namely, each cell in the main part of this table indicates the statistics of submissions from a particular student for a specific task. If there is a "+" sign in the cell, it means that the task was done successfully ("OK" or "Accepted"), if "-" — it means that the person tried to pass the task, but did not pass all the tests. The number after the icon (if any) indicates the number of unsuccessful attempts (if any). For example:

-
An empty cell indicates that the task did not give up at all;

-
"+" indicates that the task was completed on the first attempt;

+
An empty cell indicates that the student did not attempt on the task at all;

+
"+" indicates that the task was solved successfully on the first attempt;

notation "-" does not happen;

-
"+2" means that the task was passed on the third attempt (two unsuccessful attempts and then a successful one);

+
"+2" means that the task was solved on the third attempt (two unsuccessful attempts and then a successful one);

"-2" means that there were only two unsuccessful attempts on the task and that's it.

-
The background of the cell indicates the status of the task relative to the verdicts "Read" and "Ignored":
+
The background of the cell indicates the status of the task relative to the verdicts "Accepted" and "Ignored":

Yellow background — status "OK", i.e. I haven't looked at this task yet;

Green background — status "Accepted";

-
The blue background indicates the status "Ignored", while the ignored attempt itself is considered unsuccessful.

+
The blue background indicates the status "Ignored", while the ignored attempt itself is considered neither successfull nor unsuccessful.

The last two columns of the table indicate the total number of solved tasks and the total number of unsuccessful ("penalty") attempts (only those tasks for which the correct program was eventually obtained are taken into account, ignored attempts are not taken into account). The table is sorted by the total number of solved problems, and with an equal number of solved problems — by the number of penalty attempts.

-
The table also has the following feature: only schoolchildren who meet two requirements at once appear in it: first, I have to activate their account, and secondly, the student must have at least one attempt to complete the task. Therefore, if you have not tried to take anything yet, do not be surprised that you are not in the table. If you have already tried to take something, but you are not in the table, then write to me - perhaps I forgot to activate you.

+
The table also has the following feature: only students who meet two requirements at once appear in it: first, I have to activate their account, and secondly, the student must have at least one attempt to complete the task. Therefore, if you have not tried to take anything yet, do not be surprised that you are not in the table. If you have already tried to take something, but you are not in the table, then write to me - perhaps I forgot to activate you.

Similarly, problem sets (contests) they appear in the summary table only when at least someone submits some solution for some problem of this contest.

Terminology

         Above I have already introduced some specific terminology that is used in programming contests, just in case I will repeat it here:

-
A contest is any set of tasks that is somehow grouped and separated from the rest. Within the contest, the tasks are usually numbered (1, 2, 3, ... or A, B, C, ... etc.) It can be a separate round of the contest, or a set of tasks for some class or on some topic, etc. As part of our classes, we call a "contest" a set of tasks available on each individual link from a page of the appropriate level. For example, "Arithmetic Operations Tasks" is a separate contest.

-
A submission (synonyms: attempt, submit) is your separate attempt to submit a task to the testing system; also the program that you passed in this attempt.

+
A contest is any set of tasks that is somehow grouped and separated from the rest. Within the contest, the tasks are usually numbered (1, 2, 3, ... or A, B, C, ... etc.) It can be a separate round of some competition, or a set of tasks for some class or on some topic, etc. As part of our classes, we call a "contest" a set of tasks available on each individual link from a page of the appropriate level. For example, "Problems on arithmetic operations" is a separate contest.

+
A submission (synonyms: attempt, submit) is your separate attempt to submit a task to the testing system; also the program that you submitted in this attempt.

A penalty attempt (usually in the context of a summary rating) is an attempt that did not pass all the tests.

-
The test is a separate test case prepared by the authors of the task in order to test your programs on it. Usually, for each task, the author of the task prepares from 5 to 60 tests, and the programs that you submit for verification are checked in turn on all these tests. In order for the attempt to be considered successful, it is necessary that the program passes all the tests, i.e. it gives the correct answer to all the tests. (Other contests may have different rules.)

+
The test is a separate test case prepared by the authors of the task in order to test your programs on it. Usually, for each task, the author of the task prepares from 5 to 60 tests, and the programs that you submit for verification are checked in turn on all these tests. On our site, in order for the attempt to be considered successful, it is necessary that the program passes all the tests, i.e. it gives the correct answer to all the tests. (Other contests may have different rules.)

""")

 module20927_7 = () ->
@@ -265,10 +265,10 @@
                     String.raw"""
What is this course?

This is a course on algorithmic programming (algorithms, data structures, etc.)

-
It is primarily designed for schoolchildren and as a preparation for school programming contests (and many materials in the course are written exactly as for schoolchildren), but also everyone can study in absentia (and really do) in general: students, university graduates, etc. Classes for Nizhny Novgorod schoolchildren are free, for everyone else — paid.

+
It is primarily designed for schoolchildren and as a preparation for school programming contests (and many materials in the course are written exactly as for schoolchildren), but also everyone can (and do) study: students, university graduates, etc. Classes for Nizhny Novgorod schoolchildren are free, for everyone else — paid.

What is being taught here?

-
The main topic is algorithmic programming. Starting from the basics of programming, and further into algorithms and data structures. This is what is called Computer Science in English.

+
The main topic is algorithmic programming. Starting from the basics of programming, and further into algorithms and data structures.

What is NOT taught here?

We do not deal with "technical" issues:

@@ -280,48 +280,39 @@

We will study the algorithms that underlie many such programs (we will not discuss how to make the "sort alphabetically" button, but we will discuss how to implement sorting when there is already a list of objects to sort).

Are you really going to teach?

-
Yes and no. The format of the correspondence course implies that you will study a significant part of the material yourself. I will rather guide you, advising you where to look at this or that question, on which tasks to debug skills, and I will also comment on your programs quite actively. In addition, you can always contact me (contacts in the "About the course" section) and ask any question.

+
Yes and no. The format of the online course implies that you will study a significant part of the material yourself. I will rather guide you, advising you where to look at this or that question, on which tasks to debug skills, and I will also comment on your programs quite actively. In addition, you can always contact me (contacts in the "About the course" section) and ask any question.

What is the programming language?

-
Generally speaking, whatever you want, of those that allow you to write console applications and are supported by this site. These are C++, Java, Python, C#, PHP, Ruby, Perl, Pascal, Basic, even 1C.

+
Generally speaking, whatever you want, of those that allow you to write console applications and are supported by this site. These are C++, Java, Python, C#, PHP, Perl, Kotlin, etc.

At the same time, if you don't know any programming language yet, then I recommend Python. On the course page there are materials for beginners in python. If you know any of the languages listed in the previous paragraph, you can use it; but in general, all advanced topics (starting from level 2) do not depend on the language.

-
Personally, I know C++, python and pascal best of all; I can help you with these languages in sufficient detail. In other languages, I will not be able to tell you about various subtleties of the language, although, as experience shows, this is not very important for algorithms.

+
Personally, I know C++ and Python best of all; I can help you with these languages in sufficient detail. In other languages, I will not be able to tell you about various subtleties of the language, although, as experience shows, this is not very important for algorithms.

And what initial knowledge is required?

-
In programming — from none and above. In general, the correspondence format allows you to implement almost individual training (i.e., each participant of the course is engaged in his own rhythm and mode), so I expect people with a wide range of initial knowledge here: from junior students who are just beginning to program, to already professional programmers who want to improve their knowledge in the field of algorithms.

+
In programming — from none and above. In general, the online format allows you to implement almost individual training (i.e., each participant of the course is engaged in his own rhythm and mode), so I expect people with a wide range of initial knowledge here: from junior schoolchildren who are just beginning to program, to already professional programmers who want to improve their knowledge in the field of algorithms.

What should I do to start studying?

-
Register on the site (for this you will be asked to register on the site again informatics.mccme.ru ). When registering, specify your real name and the correct locality so that I can distinguish you from other users of the site.

-
Write to me using any of the methods listed in the "About the Course" section. In the letter, specify your name, where you study / work. In addition, write briefly what your experience in programming is, or you will be engaged "from scratch".

+
Register on the site.

+
Contact me using any of the methods listed in the "About the Course" section. In the letter, specify your name, where you study / work. In addition, write briefly what your experience in programming is, or you will be studying "from scratch".

Read, or at least review, all the texts in the "About the Course" section.

Wait for a response and further instructions from me.

-
When can I start studying? When is the "next set"?

+
When can I start studying? When does the "next group" start?

You can join the course at any time. All the same, students have quite different levels of training, so there is no single program like "this week we are going through this, next week this, and if you missed or learned about the course too late, then wait for the next group or next year".

The only thing is that during the holidays or in the summer I may have limited access to the Internet, so I may not respond to your letters so quickly, look at your decisions, etc.

-
Classes only by correspondence?

-
Yes, there are no full-time classes for non-schoolchildren. And if they were, they would cost much more.

-

And who are you anyway?

-
I am Petr Andreevich Kalinin, Ph.D., Senior developer at Yandex, graduate of the Nizhny Novgorod State University. At one time, I actively participated in various programming contests and olympiads: bronze medalist of the International Olympiad of Schoolchildren in Computer Science (IOI) in 2001, gold medalist of the IOI in 2002; as part of the UNN team, I twice participated in the finals of the ACM World Team Programming Championship (ACM ICPC). From 2017 to 2019, I taught at the Yandex School of Data Analysis. I am a student of V.D. Lelyukh.

+
I am Petr Kalinin, Ph.D., Senior developer at Yandex, graduate of the Nizhny Novgorod State University. At one time, I actively participated in various programming contests and olympiads: bronze medalist of the International Olympiad in Informatics (IOI) in 2001, gold medalist of the IOI in 2002; as part of the UNN team, I twice participated in the finals of the ACM International Collegiate Programming Contest (ACM ICPC). From 2017 to 2019, I taught at the Yandex School of Data Analysis. I am a student of V.D. Lelyukh.

In one form or another, I have been teaching programming to schoolchildren since I graduated from school in 2002 (I went as a teacher to various summer schools, etc.); this course has been in existence since 2013. Among my students are a number of participants and one winner of the final stage of the All—Russian Olympiad of Schoolchildren in Computer Science (not counting my brother Nikolai, in whose training I also took part not the last time and who is the absolute winner of the final stage of 2013 and twice — in 2013 and 2014 — the gold medalist of the international Olympiad of schoolchildren in computer science).

-
Teams of schoolchildren under my leadership regularly participate in the Nizhny Novgorod and All-Russian team Olympiad of schoolchildren in programming. At most of the Nizhny Novgorod team Olympiads that have taken place so far, my teams took the absolute first place; at the All—Russian Team Olympiad, my teams regularly become prize—winners, often medalists, and twice they took the absolute first place.

-

You can always contact me, contact details are listed in the "About the course" section.

-
-
And what kind of website informatics.mccme.ru ?

-
Informatics.mccme.ru — this is a site for distance learning in computer science, organized by the Moscow Center for Continuing Mathematical Education (MCNMO) and the Moscow Institute of Open Education (MIOO); it is very convenient for organizing courses like this, and, as you can see on the main page of the site, it is really widely used, but primarily for organizing face-to-face classes. I am not directly related to this site (although I am familiar with the people who created and maintain it).

-
-
My course is based on materials from informatics and uses it as a testing system (and previously the course existed only on informatics). There could still be links to computer science in some places, if you find one, write to me.

""")
+""")

 module20927_1 = () ->
     epigraph("Use your head to think!",
@@ -332,33 +323,33 @@

Moreover, you should always be critical of any information you receive, of anything that is written in textbooks, of anything I tell you or that is written on the course page. Think, try to understand why it is written, why you are advised to do so, and not otherwise. In textbooks, in what other people tell you, and even more so in my materials and advice, there can easily be mistakes, typos, and finally, you may find easier ways to achieve the same — so never take for granted what you read somewhere, always think about and reflect on the information received. It may turn out that in one source you read one information, and in another source something absolutely opposite — think, experiment (see below), and understand how to do better.

-
Also, don't be afraid to experiment! If you are not sure if something is possible in python/c++/etc., if it ok to write this or that command, whether it will work as you expect — try it: write and check. If you don't remember exactly how to write a command, try several options until you succeed. If you have read completely different information about how to do something in different places, try it and check which method works! Don't be afraid to try, don't be afraid to experiment — this way you will learn a lot more than if you just strictly follow what is written in any textbooks or what  someone (even me) told you.

""")
+
Also, don't be afraid to experiment! If you are not sure if something is possible in python/c++/etc., if it ok to write this or that command, whether it will work as you expect — try it: write and check. If you don't remember exactly how to write a command, try several options until you succeed. If you have read completely different information about how to do something in different places, try it and check which method works! Don't be afraid to try, don't be afraid to experiment — this way you will learn a lot more than if you just strictly follow what is written in any textbooks or what someone (even me) told you.

""")

 module20927_35 = () ->
     page(
         "\nAbout the license for the site materials",
                                                 String.raw"""

About the license for the materials of this site

The source code of the site

-        The source code of the actual site algoprog.ru written by me (Peter Kalinin) and distributed under the GNU Affero General Public License. You can download the source code from the website github.com/petr-kalinin/algoprog .
+        The source code of the actual site algoprog.org written by me (Petr Kalinin) and is distributed under the GNU Affero General Public License. You can download the source code from the website github.com/petr-kalinin/algoprog.

Theoretical materials

There are various theoretical materials on this site with varying degrees of clarity from the point of view of the license.

Materials in which the license is explicitly specified are distributed under the license specified in them.

-
Materials posted on other sites to which from the site algoprog.ru only the link is affixed, distributed under a license determined by the specified site.

-
Materials in which the license is not explicitly specified, but of which I am the author (this is most of the materials posted directly on the site algoprog.ru ), distributed under the Creative Commons Attribution-ShareAlike (CC BY-SA) 4.0 license.

+
Materials posted on other sites to which from the site algoprog.org only the link is affixed, are distributed under a license determined by the specified site.

+
Materials in which the license is not explicitly specified, but of which I am the author (this is most of the materials posted directly on the site algoprog.org ), are distributed under the Creative Commons Attribution-ShareAlike (CC BY-SA) 4.0 license.

If you are not sure which license applies to a particular material, ask me.

-
Task conditions

-
The terms of the tasks are taken from the website informatics.mccme.ru , where they are mostly collected from various contests. The terms of the license for them are not clear.

+
Task statements

+
Most of the the task statements are taken from the website informatics.mccme.ru , where they are mostly collected from various contests. The terms of the license for them are not clear.

""", {id: "license"})

 module20927_15 = () ->
     page(
         "\nAbout the level system",
                                       String.raw"""

About the level system

-
All the material in our course is distributed by levels, from the simplest to the most complex. The levels are numbered in consecutive numbers (1, 2, 3, ...), sublevels numbered with letters are also introduced within the level (in level 1 — sublevels 1A, 1B, 1C and 1D, etc.)

-
-
Most levels have the following structure: sublevels A and B contain new material, and sublevels C and D (the latter is not always there) — additional tasks of increased complexity to consolidate this material. Sometimes in levels C and D there may be particularly difficult tasks for the material of significantly earlier levels (for example, in level 5C there may be a difficult task for the material of level 3). Sometimes there are contests on separate topics in levels C and D, sometimes additional tasks in these levels go in random order.

+
All the material in our course is grouped into levels, from the simplest to the most complex. The levels are numbered in consecutive numbers (1, 2, 3, ...), sublevels numbered with letters are also introduced within the level (in level 1 — sublevels 1A, 1B, 1C and 1D, etc.)

+
+
Most levels have the following structure: sublevels A and B contain new material, and sublevels C and D (the latter is not always there) — additional problems of increased complexity to consolidate this material. Sometimes in levels C and D there may be particularly difficult problems for the material of significantly earlier levels (for example, in level 5C there may be a difficult problem for the material of level 3). Sometimes there are contests on separate topics in levels C and D, sometimes additional problems in these levels go in random order.

I assume that you will go through the sublevels sequentially, moving to a new sublevel usually when you have "passed" the previous sublevel. Exception: if you have passed some topic in sublevel A or B, and you see a contest of advanced tasks on the same topic in sublevel C or D, then you can solve it.


Don't try to autorank anonymous users playing autoranked ladder levels

--- app/views/play/level/tome/CastButtonView.coffee
+++ app/views/play/level/tome/CastButtonView.coffee
@@ -104,7 +104,7 @@
       @playSound 'cast-end', 0.5
       # Worked great for live beginner tournaments, but probably annoying for asynchronous tournament mode.
       myHeroID = if me.team is 'ogres' then 'Hero Placeholder 1' else 'Hero Placeholder'
-      if @autoSubmitsToLadder and not e.world.thangMap[myHeroID]?.errorsOut
+      if @autoSubmitsToLadder and not e.world.thangMap[myHeroID]?.errorsOut and not me.get('anonymous')
         _.delay (=> @ladderSubmissionView?.rankSession()), 1000 if @ladderSubmissionView
     @hasCastOnce = true
     @updateCastButton()


Add info footer

Add info footer

--- app/assets/javascripts/components/posts.js.cjsx
+++ app/assets/javascripts/components/posts.js.cjsx
@@ -40,6 +40,15 @@
           <PostPartial key={post.identifier} site={@props.site} post={post} />
         ).bind(this))}
       </Loader>
+
+
+

+
+

+

+          Octodmin, open source project by Dmitry Krasnoukhov
+

+

 )


[Group Notifications] code style fix

[Group Notifications] code style fix

--- workers/social/lib/social/models/bucket/index.coffee
+++ workers/social/lib/social/models/bucket/index.coffee
@@ -96,47 +96,38 @@
             else if rel
               konstructor = Base.constructors[rel.sourceName]
               konstructor.one _id: rel.sourceId, (err, activity)->
-                if err
-                  callback err
-                else if isOwn
-                  callback null, bucket
-                else
-                  Relationship.one
-                    sourceId : anchor.getId()
-                    targetId : activity.getId()
-                    as       : 'activity'
-                  , (err, relationship) ->
-
-                    emitBucketIsUpdated = (bucket, activity, callback) ->
-                      bucketOptions =
-                        type        : activity.constructor.name
-                        teaserId    : bucket.getId()
-                        createdAt   : bucket.meta.createdAt
-
-                      CActivity.emit 'BucketIsUpdated', bucketOptions
-                      callback null, bucket
-
-                    if err
-                      callback err
-                    else if relationship?
-                      relationship.update
-                        $set :
-                          data:
-                            flags:
-                              glanced: false
-                          timestamp: new Date
-                      , (err)->
-                          if err
-                            callback err
-                          else
-                            emitBucketIsUpdated bucket, activity, callback
-                    else
-                      anchor.addActivity activity, (err)->
-                        if err
-                          callback err
-                        else
-                          emitBucketIsUpdated bucket, activity, callback
-
+                return callback err if err
+                return callback null, bucket if isOwn
+                Relationship.one
+                  sourceId : anchor.getId()
+                  targetId : activity.getId()
+                  as       : 'activity'
+                , (err, relationship) ->
+                  return callback err if err
+                  emitBucketIsUpdated = (bucket, activity, callback) ->
+                    bucketOptions =
+                      type        : activity.constructor.name
+                      teaserId    : bucket.getId()
+                      createdAt   : bucket.meta.createdAt
+
+                    CActivity.emit 'BucketIsUpdated', bucketOptions
+                    callback null, bucket
+
+                  if relationship?
+                    relationship.update
+                      $set :
+                        data:
+                          flags:
+                            glanced: false
+                        timestamp: new Date
+                    , (err)->
+                        return callback err if err
+                        emitBucketIsUpdated bucket, activity, callback
+                  else
+                    anchor.addActivity activity, (err)->
+                      return callback err if err
+                      emitBucketIsUpdated bucket, activity, callback
+
             else
               CBucketActivity = require '../activity/bucketactivity'
               activity = CBucketActivity.create bucket


Oops: removing already implemented TODO

Oops: removing already implemented TODO

--- app/assets/javascripts/lib/ui_guiders.js.coffee
+++ app/assets/javascripts/lib/ui_guiders.js.coffee
@@ -1,10 +1,5 @@
 jQuery ($) ->

-  # TODO
-  #  * Cookies telling not to show a particular guider
-  #  * manual arrow positioning
-  #  * auto guider positioning: auto detect which side
-  #    of the screen (left-vs-right & top-vs-bottom it should appear)
   window.UIGuider = class UIGuider

     constructor: (@block) ->


using prop to enable/disable order form fields

--- app/assets/javascripts/mobile/_mobile.js.coffee
+++ app/assets/javascripts/mobile/_mobile.js.coffee
@@ -195,7 +195,7 @@
         stripeResponseHandler = (status, response) ->
             if response.error
                 alert response.error.message
-                ($ '#order_form_submit').attr('disabled', false)
+                ($ '#order_form_submit').prop('disabled', '')
             else
                 $credit_card_number.val('')
                 $credit_card_expiration_month.val('')
@@ -213,7 +213,7 @@

                 # this is really hacky, but it's all happy.js gives us to work with
                 unless ($ @).find('.unhappy').length
-                    ($ '#order_form_submit').attr('disabled', true)
+                    ($ '#order_form_submit').prop('disabled', 'disabled')
                     Stripe.createToken({
                         number: $credit_card_number.val(),
                         exp_month: $credit_card_expiration_month.val(),


fixing language selection disappearance when showing cached home view

--- app/views/kinds/RootView.coffee
+++ app/views/kinds/RootView.coffee
@@ -18,10 +18,6 @@
     'change .language-dropdown': 'showDiplomatSuggestionModal'
     'click .toggle-fullscreen': 'toggleFullscreen'

-  afterRender: ->
-    super()
-    @buildLanguages()
-
   logoutAccount: ->
     logoutUser($('#login-email').val())

@@ -41,11 +37,15 @@
     hash = location.hash
     location.hash = ''
     location.hash = hash
+    @buildLanguages()

     # TODO: automate tabs to put in hashes and navigate to them here

   buildLanguages: ->
     $select = @$el.find(".language-dropdown").empty()
+    if $select.hasClass("fancified")
+      $select.parent().find('.options,.trigger').remove()
+      $select.unwrap().removeClass("fancified")
     preferred = me.lang()
     codes = _.keys(locale)
     genericCodes = _.filter codes, (code) ->


增加文件存在判断

增加文件存在判断

--- lib/build/build-manager.coffee
+++ lib/build/build-manager.coffee
@@ -28,7 +28,7 @@
     # 这样就可以可以不仅在windows还是mac都可以获取到.atom路径
     decs = "#{atom.getConfigDirPath()}/atom-butterfly"
     zipFile = "#{uuid.v1()}.zip"
-    unless fs.statSync(decs).isDirectory()
+    unless fs.existsSync(decs) and fs.statSync(decs).isDirectory()
       console.log "新建文件夹：#{decs}"
       fs.mkdirSync decs
     removeZipPath = "#{decs}/#{zipFile}"


grammar fix - sub total -> subtotal

grammar fix - sub total -> subtotal

--- assets/coffeescript/pupil_store.coffee
+++ assets/coffeescript/pupil_store.coffee
@@ -266,7 +266,7 @@
                     "</div>"

           $("#Cart-table").after(newRow)
-        [totalPrice,label] = if LocalStorage.length() > 0 then [getProductsSum(v.order,v.qty) for k,v of LocalStorage.dict(),"Sub Total"] else ["",""]
+        [totalPrice,label] = if LocalStorage.length() > 0 then [getProductsSum(v.order,v.qty) for k,v of LocalStorage.dict(),"Subtotal"] else ["",""]
         totalPrice = if totalPrice.length > 0 then "€ " + _sumAll(totalPrice)
         $("h3[id='CartSum--label']").text("#{ label }")
         $("h3[id='CartSum--total']").text("#{ totalPrice }")


:fire: Remove PackageDetailView::installUpdate()


Hide open button for bundled packages

Hide open button for bundled packages

Bundled packages are stored in a binary .asar file and so their
source can no longer opened directly.


set references to undefined so its not used yet

--- server-ce/settings.coffee
+++ server-ce/settings.coffee
@@ -388,6 +388,7 @@
 			url: "http://localhost:3000"
 			user: httpAuthUser
 			pass: httpAuthPass
+	references:undefined
 	# 	documentupdater:
 	# 		url : "http://localhost:#{docUpdaterPort}"
 	# 	clsi:


Fix failing test when using null Transport

--- lib/devicecommunication.coffee
+++ lib/devicecommunication.coffee
@@ -163,10 +163,11 @@
         @transport = transport
         @componentLib = componentLib
         @accumulator = new CommandAccumulator commandstream.cmdFormat.commandSize
+
+        return if not @transport
         @sender = new SendQueue commandstream.cmdFormat.commandSize,
           type: @transport.getTransportType()

-        return if not @transport
         @transport.on 'data', (buf) =>
             @accumulator.onData buf
         @accumulator.on 'command', (buf) =>


Pop up the signup screen when clicking "Login to see price"

--- apps/auction_lots/client.coffee
+++ apps/auction_lots/client.coffee
@@ -1,4 +1,5 @@
-ZoomView = require '../../components/modal/zoom.coffee'
+ZoomView  = require '../../components/modal/zoom.coffee'
+mediator  = require '../../lib/mediator.coffee'

 module.exports.init = ->
   $ ->


fixed startup error looking for completions when client === null

--- lib/atom-ternjs.coffee
+++ lib/atom-ternjs.coffee
@@ -46,15 +46,17 @@
       position = cursor.getBufferPosition()
       row = position.row
       col = position.column
-      client.completions(editor.getUri(),
-        line: row
-        ch: col
-      editor.getText()).then (data) =>
-        if data.completions.length
-          {@start, @end} = data
-          @atomTernjsView.startCompletion(data.completions)
-      , (err) ->
-        console.error 'error', err
+      console.log client
+      if client
+        client.completions(editor.getUri(),
+          line: row
+          ch: col
+        editor.getText()).then (data) =>
+          if data.completions.length
+            {@start, @end} = data
+            @atomTernjsView.startCompletion(data.completions)
+        , (err) ->
+          console.error 'error', err

   findDefinition: ->
     editor = atom.workspace.getActiveEditor()
@@ -106,7 +108,7 @@
       @ternPort = port
       client = new ClientFactory(port)
       atom.workspaceView.command 'tern:completion', =>
-        @checkCompletion(atom.workspace.getActiveEditor(), yes)
+        @checkCompletion(atom.workspace.getActiveEditor(), false)
       atom.workspaceView.command 'tern:definition', =>
         @findDefinition(atom.workspace.getActiveEditor())


Only require guid when actually using it

--- lib/exception-reporting.coffee
+++ lib/exception-reporting.coffee
@@ -1,9 +1,10 @@
-Guid = require 'guid'
 Reporter = null

 module.exports =
   activate: ->
-    atom.config.set('exception-reporting.userId', Guid.raw()) unless atom.config.get('exception-reporting.userId')
+    unless atom.config.get('exception-reporting.userId')
+      atom.config.set('exception-reporting.userId', require('guid').raw())
+
     @uncaughtErrorSubscription = atom.on 'uncaught-error', (message, url, line) ->
       Reporter ?= require './reporter'
       Reporter.send(message, url, line)


Ensure authToken is populated

Ensure authToken is populated

--- app/frontend/coffee/localdoc/application.coffee
+++ app/frontend/coffee/localdoc/application.coffee
@@ -7,7 +7,7 @@
 extensionFormatMap = require "localdoc/extension_format_map.json"

 $ ->
-  pageData.model.authToken = $('meta[name="csrf-token"]').attr('content')
+  pageData.model.authToken = $('meta[name="csrf-token"]').attr('content') || 'dummyToken'

   app = Elm.embed Elm.Localdoc.API, document.getElementById("elm-host"),
     modelJson: pageData.model


.

Former-commit-id: a8b9f35884a7eb78068e15edc8d3a13831524029 [formerly 62a1534a9be8b205edea0c31b56f12ab9d632321] [formerly 34f33b7eb73b3611cb8e919216c2161675827df2 [formerly 5ee5b2073c69680c523297b92a0986c04fe2a83b]] [formerly c3496b060dbe579b25c00fb4927d01610b1a57ea [formerly e2aa9e696bd84b657a69215cb5c32bc865874ca5] [formerly d5fab9ff423d5b56a78e3f412133147f9a5eaf5f [formerly cafeb0a97c79f227cbb40e9f778e548acfcb6e70]]]
Former-commit-id: 488fea5197a41fcec545c01d739d157ed026ee90 [formerly d66b3b9353d63118db4e8d90382b5db96ce51565] [formerly d3101c7385c3817aad048a849cbee1b14806ed4d [formerly eea11962a7412a4ec217d3728000239211069a41]]
Former-commit-id: dd207ca27d643c8780aab3b3523b73476a06a5d1 [formerly f9331f152782896e88269e69db29c5051e97037d]
Former-commit-id: a3faee1464f716e2d41d1550066563b1eb28e981
Former-commit-id: 48b7bec599bcf110f7f5f4e195d1876f20d4ffa9

--- @client/customizations.coffee
+++ @client/customizations.coffee
@@ -2135,7 +2135,7 @@
               marginTop: 10
               padding: 8

-            "This site is undergoing a sybil attack. Filtering to verified users has temporarily been enabled by default."
+            "A sybil attack has been demonstrated on this site. Filtering to verified users has temporarily been enabled by default."
             " "

         ProfileMenu()


kite: explicit call to disconnect should not trigger reconnection

--- src/kite/kite.coffee
+++ src/kite/kite.coffee
@@ -54,7 +54,7 @@
     @emit 'info', "Trying to connect to #{ url }"
     return

-  disconnect: (reconnect = true) ->
+  disconnect: (reconnect = false) ->
     @autoReconnect = !!reconnect
     @ws.close()
     @emit 'info', "Disconnecting from #{ @options.url }"


remove unused code

remove unused code

--- vue/src/routes.coffee
+++ vue/src/routes.coffee
@@ -48,14 +48,6 @@
   {path: 'files', component: GroupFilesPanel, meta: {noScroll: true}}
   {path: 'settings', component: GroupSettingsPanel, meta: {noScroll: true}}
   {path: ':stub?', component: GroupDiscussionsPanel, meta: {noScroll: true}}
-]
-
-# threadPageChildren =
-
-strandPageChildren = [
-  {path: 'comment/:comment_id'}
-  {path: ':stub?/:sequence_id?'}
-  {path: ''}
 ]

 router = new Router


:memo: More documentation for the MinimapRenderView

--- lib/minimap-render-view.coffee
+++ lib/minimap-render-view.coffee
@@ -299,9 +299,23 @@
   #    ##    ## ##     ## ##       ##     ## ##    ##  ##    ##
   #     ######   #######  ########  #######  ##     ##  ######

+  # Returns the default text color for an editor content.
+  #
+  # The color value is directly read from the `EditorView` computed
+  # styles.
+  #
+  # Returns a {String}.
   getDefaultColor: ->
     @transparentize(@minimapView.editorView.css('color'), @getTextOpacity())

+  # Returns the text color for the passed-in `token` object.
+  #
+  # The color value is read from the DOM by creating a node structure
+  # that match the token `scope` property.
+  #
+  # token - A token {Object}.
+  #
+  # Returns a {String}.
   getTokenColor: (token) ->
     #Retrieve color from cache if available
     flatScopes = token.scopes.join()
@@ -310,6 +324,15 @@
       @tokenColorCache[flatScopes] = color
     @tokenColorCache[flatScopes]

+  # Returns the background color for the passed-in `decoration` object.
+  #
+  # The color value is read from the DOM by creating a node structure
+  # that match the decoration `scope` property unless the decoration
+  # provides its own `color` property.
+  #
+  # decoration - A `Decoration` object.
+  #
+  # Returns a {String}.
   getDecorationColor: (decoration) ->
     properties = decoration.getProperties()
     return properties.color if properties.color?
@@ -318,8 +341,32 @@
       @decorationColorCache[properties.scope] = color
     @decorationColorCache[properties.scope]

-  # This function insert a dummy token element in the DOM to compute its style,
-  # return the specified property, and remove the element from the DOM.
+  # Internal: Returns the text color for the passed-in token.
+  #
+  # token - A token {Object}.
+  #
+  # Returns a {String}.
+  retrieveTokenColorFromDom: (token) ->
+    # This is quite an expensive operation so results are cached in getTokenColor.
+    color = @retrieveStyleFromDom(token.scopes, 'color')
+    @transparentize(color, @getTextOpacity())
+
+  # Internal: Returns the background color for the passed-in decoration.
+  #
+  # decoration - A `Decoration` object.
+  #
+  # Returns a {String}.
+  retrieveDecorationColorFromDom: (decoration) ->
+    @retrieveStyleFromDom(decoration.getProperties().scope.split(/\s+/), 'background-color')
+
+  # Internal: This function insert a dummy element in the DOM to compute
+  # its style, return the specified property, and remove the element
+  # from the DOM.
+  #
+  # scopes - An {Array} of {String} reprensenting the scope to reproduce.
+  # property - The property {String} name.
+  #
+  # Returns a {String} of the property value.
   retrieveStyleFromDom: (scopes, property) ->
     @ensureDummyNodeExistence()

@@ -337,22 +384,23 @@

     value

-  retrieveTokenColorFromDom: (token) ->
-    # This is quite an expensive operation so results are cached in getTokenColor.
-    color = @retrieveStyleFromDom(token.scopes, 'color')
-    @transparentize(color, @getTextOpacity())
-
-  retrieveDecorationColorFromDom: (decoration) ->
-    @retrieveStyleFromDom(decoration.getProperties().scope.split(/\s+/), 'background-color')
-
+  # Internal: Creates a DOM node container for all the operations that
+  # need to read styles properties from DOM.
   ensureDummyNodeExistence: ->
     unless @dummyNode?
       @dummyNode = document.createElement('span')
       @dummyNode.style.visibility = 'hidden'
       @editorView.append(@dummyNode)

+  # Internal: Converts a `rgb(...)` color into a `rgba(...)` color
+  # with the specified opacity.
+  #
+  # color - The {String} of the color to modify.
+  # opacity - The opacity {Number} to apply to the color.
+  #
+  # Returns a {String}.
   transparentize: (color, opacity=1) ->
-    color.replace('rgb', 'rgba').replace(')', ", #{opacity})")
+    color.replace('rgb(', 'rgba(').replace(')', ", #{opacity})")

   #    ########  ########     ###    ##      ##
   #    ##     ## ##     ##   ## ##   ##  ##  ##


Remove accidentaly added file.

Remove accidentaly added file.

--- lib/connection_.coffee
+++ lib/connection_.coffee
@@ -1,3 +0,0 @@
-EventEmitter = require('events').EventEmitter
-
-class Connection extends EventEmitter


Fixed to indentation

Fixed to indentation


seattle update

seattle update

Former-commit-id: f4847ad3cb4efa61df7006d8d7f0eae2911c0d07 [formerly c7d7a66706176b377ba377687fcbbff9f9dc5bd2] [formerly f4b986705e8ab2589eac5c008543eabc79be08a7 [formerly de8f748af46047c23291b6f6f2e0fe88271ba0c6]] [formerly 1f2c99c904c95bae41dd39e1d2308c209c6a7d45 [formerly 0ec0a09bed7069405cd28be829c5c719dfad6ac8] [formerly c5a767006465b059bca6a610ebeb0d4a42419677 [formerly c5a767006465b059bca6a610ebeb0d4a42419677 [formerly f4efd1f686c1c86d7db7ec5f2db00c830a027a1f]]]]
Former-commit-id: 309f599b7319148903f251a7692b51309c584ca4 [formerly 5468562fa080c9f9257162cbce3e69ec6d008562] [formerly f78a3d5e9f0fb75cfe45e1edc28310d68fcf5efa [formerly e8cf331fb7efc5a5f31c5448b26bea647254ce18]]
Former-commit-id: 4f786920cddb042ea99fe0531dc7aed036c014ac [formerly da1dff91367a422ea894b04ed2646574f4ebe999]
Former-commit-id: 797c3b8e0bfaadfcdfc8d00855d884b4adaf0b19
Former-commit-id: fada62945d988a59b0ea379172e51d24f423267f

--- @client/customizations.coffee
+++ @client/customizations.coffee
@@ -582,6 +582,7 @@

 seattle2035_cream = "#FCFBE6"
 seattle2035_pink = '#F06668'
+seattle2035_dark = '#5C1517'

 customizations['seattle2035'] =
   point_labels : pro_con
@@ -599,6 +600,28 @@
     displayName: 'HomepageHeader'

     render: ->
+
+
+
+      header_style =
+        color: seattle2035_pink
+        fontSize: 44
+        #fontWeight: 600
+        marginTop: 10
+
+      section_style =
+        marginBottom: 20
+        color: seattle2035_dark
+
+      paragraph_heading_style =
+        display: 'block'
+        #fontWeight: 600
+        fontSize: 28
+        color: seattle2035_pink
+
+      paragraph_style =
+        fontSize: 18
+
       DIV
         style:
           position: 'relative'
@@ -609,7 +632,7 @@
           style:
             display: 'block'
             position: 'absolute'
-            top: 10
+            top: 22
             left: 20
             color: seattle2035_pink

@@ -643,64 +666,142 @@
               width: SIMPLE_HOMEPAGE_WIDTH()
               margin: 'auto'

-
             DIV
-              style:
-                color: seattle2035_pink
-                fontSize: 48
-                fontWeight: 600
-                margin: '10px 0'
-
-              'Seattle 2035 Draft Plan Conversation – a place to consider Big Changes.'
+              style: header_style
+
+              'We need Big Changes to address growth'

             DIV
-              style:
-                fontSize: 22
-
-
-              P
-                style:
-                  marginTop: 20
-
+              style: section_style
+
+              # SPAN
+              #   style: paragraph_heading_style
+
+              #   'Seattle is one of the fastest growing cities in America.'
+
+              SPAN
+                style: paragraph_style
+
                 """
-                Seattle is one of the fastest growing cities in America, and more growth is
-                coming– 120,000 more people and 115,000 more jobs by 2035. The time to plan
-                for how and where that growth occurs is now. After months of working with city
-                departments and the Seattle Planning Commission, we are pleased to present
-                a Draft Plan for public discussion. The full Draft Plan can be found """
+                Seattle is one of the fastest growing cities in America,
+                adding 120,000 people and 115,000 jobs by 2035. We
+                must plan for how and where that growth occurs now.
+                """
+
+            DIV
+              style: section_style
+
+
+              SPAN
+                style: paragraph_heading_style
+                'The Seattle 2035 draft plan addresses Seattle’s growth'
+
+              SPAN
+                style: paragraph_style
+                'We are pleased to present a '
+
                 A
                   target: '_blank'
                   href: 'http://www.seattle.gov/dpd/cs/groups/pan/@pan/documents/web_informational/p2273574.pdf'
                   style:
                     textDecoration: 'underline'

-                  'here'
-                "."
-
-              P
-                style:
-                  marginTop: 20
+                  'Draft Plan'

                 """
-                The Draft Plan contains hundreds of goals and policies. Many are not much
-                different than our current plan. Over the last year, people have suggested
-                bigger changes in the direction of planning for our city. The Draft Plan is
-                an opportunity to discuss the pros and cons of these directions, and
-                determine if they make sense for Seattle over the coming twenty years."""
-
-              P
-                style:
-                  marginTop: 20
+                 for public discussion. The draft is the result of months
+                of working with city departments and the Seattle Planning Commission. It contains
+                hundreds of goals and policies, including a number of Big Changes in how we plan
+                for our city."""
+
+
+            DIV
+              style: section_style
+
+              SPAN
+                style: paragraph_heading_style
+                'Please give feedback on the Big Changes proposed in the draft'
+
+              SPAN
+                style: paragraph_style

                 """
-                Is there another Big Change you would like to add to the Draft Plan
-                Conversation? Email us at """
-                A
-                  href: "mailto:[email]@seattle.gov"
-                  style:
-                    textDecoration: 'underline'
-
-                  "[email]@seattle.gov"
+                We have listed below some of the Big Changes proposed in the draft.
+                Add your opinion about whether each change makes sense for
+                Seattle over the coming twenty years. Your input will inform the next draft!
+                """
+
+            # DIV
+            #   style: section_style
+
+            #   """
+            #   Is there another Big Change you would like to add to the Draft Plan
+            #   Conversation? Email us at """
+            #   A
+            #     href: "mailto:[email]@seattle.gov"
+            #     style:
+            #       textDecoration: 'underline'
+
+            #     "[email]@seattle.gov"
+
+
+            # DIV
+            #   style:
+            #     color: seattle2035_pink
+            #     fontSize: 48
+            #     fontWeight: 600
+            #     margin: '10px 0'
+
+            #   'Seattle 2035 Draft Plan Conversation – a place to consider Big Changes.'
+
+            # DIV
+            #   style:
+            #     fontSize: 22
+
+
+            #   P
+            #     style:
+            #       marginTop: 20
+
+            #     """
+            #     Seattle is one of the fastest growing cities in America, and more growth is
+            #     coming– 120,000 more people and 115,000 more jobs by 2035. The time to plan
+            #     for how and where that growth occurs is now. After months of working with city
+            #     departments and the Seattle Planning Commission, we are pleased to present
+            #     a Draft Plan for public discussion. The full Draft Plan can be found """
+            #     A
+            #       target: '_blank'
+            #       href: 'http://www.seattle.gov/dpd/cs/groups/pan/@pan/documents/web_informational/p2273574.pdf'
+            #       style:
+            #         textDecoration: 'underline'
+
+            #       'here'
+            #     "."
+
+            #   P
+            #     style:
+            #       marginTop: 20
+
+            #     """
+            #     The Draft Plan contains hundreds of goals and policies. Many are not much
+            #     different than our current plan. Over the last year, people have suggested
+            #     bigger changes in the direction of planning for our city. The Draft Plan is
+            #     an opportunity to discuss the pros and cons of these directions, and
+            #     determine if they make sense for Seattle over the coming twenty years."""
+
+            #   P
+            #     style:
+            #       marginTop: 20
+
+            #     """
+            #     Is there another Big Change you would like to add to the Draft Plan
+            #     Conversation? Email us at """
+            #     A
+            #       href: "mailto:[email]@seattle.gov"
+            #       style:
+            #         textDecoration: 'underline'
+
+            #       "[email]@seattle.gov"

             # DIV
@@ -735,10 +836,10 @@

             DIV
               style:
-                textDecoration: 'italics'
-                float: 'left'
+                fontStyle: 'italic'
                 marginTop: 20
-                fontSize: 20
+                fontSize: 18
+                color: seattle2035_dark

               "Thanks for your time,"

@@ -749,11 +850,22 @@
                 "Department of Planning & Development"
                 BR()
                 "City of Seattle"
+
+
             DIV
-              style:
-                clear: 'both'
-
-
+              style: _.extend {}, section_style,
+                margin: 0
+                marginTop: 10
+                fontSize: 18
+
+              'p.s. Email us at '
+              A
+                href: "mailto:[email]@seattle.gov"
+                style:
+                  textDecoration: 'underline'
+
+                "[email]@seattle.gov"
+              " if there is another Big Change you would like us to add below."


Activity: widget minor interface change

Activity: widget minor interface change

--- client/Social/Activity/views/inputwidget.coffee
+++ client/Social/Activity/views/inputwidget.coffee
@@ -51,7 +51,6 @@

     @input.on 'Escape', @bound 'reset'
     @input.on 'Enter',  @bound 'submit'
-    @input.on 'Enter', => @input.setBlur()

     @on 'SubmitStarted', => @hidePreview()  if @preview

@@ -205,9 +204,7 @@
     @unsetClass "preview-active"

-  focus: ->
-    @input.setFocus()
-
+  focus: -> @input.setFocus()

   viewAppended: ->


heroku setup

heroku setup

--- web.coffee
+++ web.coffee
@@ -1,7 +1,8 @@
 gzippo = require 'gzippo'
 express = require 'express'
+morgan = require 'morgan'

 app = express()
-app.use express.morgan 'dev'
+app.use morgan('dev')
 app.use gzippo.staticGzip "#{__dirname}/dist"
 app.listen process.env.PORT || 5000


Only block hide via esc when input is focused

--- source/javascripts/jquery.windoze.js.coffee
+++ source/javascripts/jquery.windoze.js.coffee
@@ -58,7 +58,6 @@
       @$modal.find(':input').eq(0).focus()
     hide: (e) ->
       e && e.preventDefault()
-      if @$modal.find(':focus').length then return
       @$modal.removeClass('wdz-active')
       @$overlay.removeClass('wdz-active')
       if @duration.modal then setTimeout $.proxy(@hideModal, @), @duration.modal else @hideModal()
@@ -81,7 +80,7 @@
       $(@).data('windoze').hide()
     keydownHandler: (e) ->
       if e.which == 27
-        @hide()
+        if !@$modal.find(':focus').length then @hide()
     bindModalEvents: ->
       @$overlay.on('click.wdz', $.proxy(@hide, @))
       @$modal.on('click.wdz', 'a[data-wdz-close]', $.proxy(@hide, @))


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/engine.coffee
+++ src/engine.coffee
@@ -106,7 +106,6 @@
       FPS: 60
       paused: false
       showFPS: false
-      zSort: true

     frameAdvance = false


Documentation

Documentation

--- src/engine.coffee
+++ src/engine.coffee
@@ -285,6 +285,16 @@
       paused: ->
         I.paused

+      ###*
+      Change the framerate of the game. The default framerate is 30 fps.
+
+

+      engine.setFramerate(60)
+

+
+      @methodOf Engine#
+      @name pause
+      ###
       setFramerate: (newFPS) ->
         I.FPS = newFPS
         self.stop()


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/engine.coffee
+++ src/engine.coffee
@@ -141,7 +141,6 @@
       if !I.paused || frameAdvance
         elapsedTime = (1 / I.FPS)
         update(elapsedTime)
-        I.age += elapsedTime

       draw()


Complete re-implementation of all actions.

--- lib/actions.coffee
+++ lib/actions.coffee
@@ -3,6 +3,7 @@
 async = require 'async'
 roost = require 'roost'
 logsmith = require 'logsmith'
+shell_quote = require 'shell-quote'

 # ---

@@ -10,379 +11,14 @@

 # ---

-`
-// ---
-
-var helpers = {};
-
-// ---
-
-(function (exports) {
-	function error(args) {
-		if (Array.isArray(args)) {
-			return new Error(args.join(' '));
-		} else {
-			return new Error(Array.prototype.slice.call(arguments).join(' '));
-		}
-	}
-
-	// ---
-
-	function quote(input) {
-		return JSON.stringify(input);
-	}
-
-	// ---
-
-	function camel(input) {
-		return input[0] + input.substring(1, input.length);
-	}
-
-	// ---
-
-	function merge(a, b) {
-		Object.keys(b).forEach(function (key) {
-			if (a.hasOwnProperty(key)) {
-				if (typeof(a[key]) == 'boolean' || a[key] instanceof Boolean) {
-					a[key] = b[key];
-				} else
-				if (typeof(a[key]) == 'number' || a[key] instanceof Number) {
-					a[key] = b[key];
-				} else
-				if (typeof(a[key]) == 'string' || a[key] instanceof String) {
-					a[key] = b[key];
-				} else
-				if (Array.isArray(a[key])) {
-					a[key] = a[key].concat(b[key]);
-				} else {
-					a[key] = arguments.callee(a[key], b[key]);
-				}
-			} else {
-				a[key] = b[key];
-			}
-		});
-
-		return a;
-	}
-
-	// ---
-
-	exports.error = error;
-	exports.quote = quote;
-	exports.camel = camel;
-	exports.merge = merge;
-
-	// ---
-
-	exports.e = error;
-	exports.q = quote;
-	exports.c = camel;
-	exports.m = merge;
-})(helpers);
-
-// ---
-
-function provision(opt, manifest, provider, nodes, callback) {
-	var mergeRoost = function (manifest, configs) {
-		if (configs.length == 0) {
-			return null;
-		}
-
-		return configs
-			.map(function (config) {
-				if (typeof(config) == 'string' || config instanceof String) {
-					return roost.manifest.load(path.resolve(path.dirname(manifest.meta.location), config));
-				} else {
-					return config;
-				}
-			})
-			.reduce(function (previousValue, currentValue, index, array) {
-				if (!previousValue) {
-					return JSON.parse(JSON.stringify(currentValue));
-				}
-
-				if (currentValue.hasOwnProperty('merge') && currentValue.merge) {
-					return helpers.merge(previousValue, currentValue);
-				} else {
-					return currentValue;
-				}
-			}, null);
-	};
-
-	var configureRoost = function (node, callback) {
-		var nodeManifest = manifest.nodes[node];
-		var mergeConfigs = [];
-
-		if (manifest.hasOwnProperty('roost')) {
-			mergeConfigs.push(manifest.roost);
-		}
-
-		if (nodeManifest.hasOwnProperty('roost')) {
-			mergeConfigs.push(nodeManifest.roost);
-		}
-
-		if (nodeManifest.hasOwnProperty(provider.name) && nodeManifest[provider.name].hasOwnProperty('roost')) {
-			mergeConfigs.push(nodeManifest[provider.name].roost);
-		}
-
-		var roostManifest = mergeRoost(manifest, mergeConfigs);
-
-		if (!roostManifest) {
-			return callback(helpers.q('no roost configuration defined for node', helpers.q(node)));
-		}
-
-		if (mergeConfigs.length > 0 && !roostManifest.hasOwnProperty('meta')) {
-			roostManifest.meta = {
-				location: manifest.meta.location
-			};
-		}
-
-		var roostPlugins;
-
-		try {
-			roostPlugins = roost.plugins.obtain(roostManifest);
-		} catch (e) {
-			return callback(e);
-		}
-
-		nodeManifest.roost = roostManifest;
-
-		return callback(null, node, roostManifest, roostPlugins);
-	};
-
-	var bootstrapProvider = function (node, roostManifest, roostPlugins, callback) {
-		provider.bootstrap(node, function (err) {
-			if (err) {
-				return callback(err);
-			}
-
-			return callback(null, node, roostManifest, roostPlugins);
-		});
-	};
-
-	var obtainShellSpec = function (node, roostManifest, roostPlugins, callback) {
-		provider.shell_spec(node, function (err, spec) {
-			if (err) {
-				return callback(err);
-			}
-
-			return callback(null, node, roostManifest, roostPlugins, spec);
-		});
-	};
-
-	var exposeNodes = function (node, roostManifest, roostPlugins, spec, callback) {
-		if (!roostManifest.hasOwnProperty('bootstrap')) {
-			roostManifest.bootstrap = [];
-		}
-
-		roostManifest.bootstrap.push('sudo mkdir -p /etc/vortex/nodes/');
-
-		async.map(
-			Object.keys(manifest.nodes),
-
-			function (node, callback) {
-				provider.status(node, function (err, state, address) {
-					if (err) {
-						return callback(err);
-					}
-
-					return callback(null, {node: node, address: address})
-				});
-			},
-
-			function (err, results) {
-				if (err) {
-					return callback(err);
-				}
-
-				results
-					.filter(function (result) {
-						return result.node != node;
-					})
-					.filter(function (result) {
-						if (result.address) {
-							return true;
-						} else {
-							logsmith.error('node', helpers.q(result.node), 'does not expose address');
-
-							return false;
-						}
-					})
-					.map(function (result) {
-						var n = result.node;
-						var a = result.address;
-						var q = roost.shell.quote;
-
-						roostManifest.bootstrap.push('echo ' + q(a) + ' | sudo tee ' + q('/etc/vortex/nodes/' + n));
-					});
-
-				return callback(null, node, roostManifest, roostPlugins, spec);
-			}
-		);
-	};
-
-	var launchRoost = function (node, roostManifest, roostPlugins, spec, callback) {
-		var roostTarget;
-
-		try {
-			roostTarget = roost.targets.instance(spec, roostManifest);
-		} catch (e) {
-			return callback(e);
-		}
-
-		var roostOpt = {
-			options: {},
-			argv: []
-		};
-
-		if (opt.options.hasOwnProperty('dry')) {
-			roostOpt.options.dry = opt.options.dry;
-		}
-
-		try {
-			roost.engine.launch(roostOpt, roostManifest, roostPlugins, roostTarget, function (err) {
-				if (err) {
-					return callback(err);
-				}
-
-				return callback();
-			});
-		} catch (e) {
-			return callback(e);
-		}
-	};
-
-	var processNode = function (node, callback) {
-		logsmith.info('provision node', helpers.q(node));
-
-		async.waterfall(
-			[
-				function (callback) {
-					return callback(null, node);
-				},
-
-				configureRoost,
-				bootstrapProvider,
-				obtainShellSpec,
-				exposeNodes,
-				launchRoost
-			],
-			callback
-		);
-	};
-
-	async.each(nodes, processNode, callback);
-}
-
-
-function shell(opt, manifest, provider, nodes, callback) {
-	var obtainShellSpec = function (node, callback) {
-		provider.shell_spec(node, function (err, spec) {
-			if (err) {
-				return callback(err);
-			}
-
-			if (!spec.match(/^ssh:/i)) {
-				return callback(helpers.e('unsupported shell specification', spec));
-			}
-
-			return callback(null, spec);
-		});
-	};
-
-	var connect = function (spec, callback) {
-		var connection = new roost.ssh.Connection({meta: {location: manifest.meta.location}});
-
-		connection.connect(spec, function (err) {
-			if (err) {
-				return callback(err);
-			}
-
-			return callback(null, connection);
-		});
-	};
-
-	var shell = function (connection, callback) {
-		var command = opt.argv.slice(opt.argv.indexOf('--') + 1);
-
-		if (command.length == opt.argv.length) {
-			command = null;
-		} else {
-			command = command.join(' ');
-		}
-
-		if (command) {
-			connection.exec(command, function (err, stream) {
-				if (err) {
-					return callback(err);
-				}
-
-				stream.on('data', function (data) {
-					process.stdout.write(data);
-				});
-
-				stream.on('error', function (err) {
-					callback(error);
-				});
-
-				stream.on('exit', function (code) {
-					callback(null, connection);
-				});
-			});
-		} else {
-			connection.shell(function (err) {
-				if (err) {
-					return callback(err);
-				}
-
-				return callback(null, connection);
-			});
-		}
-	};
-
-	var disconnect = function (connection, callback) {
-		connection.disconnect({}, function (err) {
-			if (err) {
-				return callback(err);
-			}
-
-			return callback();
-		});
-	};
-
-	var processNode = function (node, callback) {
-		logsmith.info('shell into node', helpers.q(node));
-
-		async.waterfall(
-			[
-				function (callback) {
-					return callback(null, node);
-				},
-
-				obtainShellSpec,
-				connect,
-				shell,
-				disconnect
-			],
-			callback
-		);
-	};
-
-	async.eachSeries(nodes, processNode, callback);
-}
-
-// ---
-
-exports.provision = provision;
-exports.shell = shell;
-`
-
-# ---
-
 exports.status = (opt, manifest, provider, node_names, callback) ->
 	###
 	This action obtains the status of nodes.
 	###
+
+	#
+	# Call provider's status for each node.
+	#
 	process_node = (node_name, callback) ->
 		logsmith.verbose "query status for node #{node_name}"

@@ -403,10 +39,34 @@

 # ---

+exports.shellspec = (opt, manifest, provider, node_names, callback) ->
+	###
+	This action obtains the shell spec of nodes.
+	###
+
+	#
+	# Call provier's shell_spec for each node.
+	#
+	process_node = (node_name, callback) ->
+		provider.shell_spec node_name, (err, spec) ->
+			return callback err if err
+
+			logsmith.info node_name, '->', spec
+
+			return callback null, spec
+
+	async.eachSeries node_names, process_node, callback
+
+# ---
+
 exports.boot = (opt, manifest, provider, node_names, callback) ->
 	###
 	This action boots nodes.
 	###
+
+	#
+	# Call provider's boot for each node.
+	#
 	process_node = (node_name, callback) ->
 		logsmith.verbose "halt node #{node_name}"

@@ -433,6 +93,10 @@
 	###
 	This action halts nodes.
 	###
+
+	#
+	# Call provider's halt for each node.
+	#
 	process_node = (node_name, callback) ->
 		logsmith.verbose "halt node #{node_name}"

@@ -461,16 +125,25 @@
 	###
 	actions = []

+	#
+	# Invoke the halt method.
+	#
 	actions.push (node_name, callback) ->
 		exports.halt opt, manifest, provider, [node_name], (err) ->
 			return callback err if err
 			return callback null, node_name

+	#
+	# Invoke the boot method.
+	#
 	actions.push (node_name, callback) ->
 		exports.boot opt, manifest, provider, [node_name], (err) ->
 			return callback err if err
 			return callback null, node_name

+	#
+	# Invoke actions.
+	#
 	process_node = (node_name, callback) ->
 		logsmith.verbose "restart node #{node_name}"

@@ -482,10 +155,162 @@

 # ---

+exports.provision = (opt, manifest, provider, node_names, callback) ->
+	###
+	This action start the provisioner on the selected nodes.
+	###
+	actions = []
+
+	#
+	# Merges two objects.
+	#
+	merge_objects = (a, b) ->
+		for key, value of b
+			if a[key]?
+				switch
+					when typeof(a[key]) == 'boolean' || a[key] instanceof Boolean then a[key] = b[key]
+					when typeof(a[key]) == 'number' || a[key] instanceof Number then a[key] = b[key]
+					when typeof(a[key]) == 'string' || a[key] instanceof String then a[key] = b[key]
+					when Array.isArray(a[key]) then a[key] = a[key].concat b[key]
+					else a[key] = arguments.callee a[key], b[key]
+			else
+				a[key] = b[key]
+
+		return a
+
+	#
+	# Merges roost configs.
+	#
+	merge_roost = (manifest, configs) ->
+		return null if configs.length == 0
+
+		return configs
+			.map(((config) ->
+				if typeof(config) == 'string' || config instanceof String
+					return roost.manifest.load path.resolve(path.dirname(manifest.meta.location), config)
+				else
+					return config
+			))
+			.reduce(((previousValue, currentValue, index, array) ->
+				return JSON.parse JSON.stringify(currentValue) if not previousValue
+
+				if currentValue.merge? and currentValue.merge
+					return merge_objects previousValue, currentValue
+				else
+					return currentValue
+			), null)
+
+	#
+	# Setup some defaults.
+	#
+	actions.push (node_name, callback) ->
+		node_manifest = manifest.nodes[node_name]
+		merge_configs = []
+
+		merge_configs.push manifest.roost if manifestroost?
+		merge_configs.push node_manifest.roost if node_manifest.roost?
+		merge_configs.push node_manifest[provider.name].roost if node_manifest[provider.name]?.roost?
+
+		roost_manifest = merge_roost manifest, merge_configs
+
+		return callback new Error "no roost configuration defined for node #{node_name}" if not roost_manifest
+
+		if merge_configs.length > 0 and not roost_manifest.meta?
+			roost_manifest.meta =
+				location: manifest.meta.location
+
+		try
+			roost_plugins = roost.plugins.obtain roost_manifest
+		catch e
+			return callback e
+
+		node_manifest.roost = roost_manifest
+
+		return callback null, node_name, roost_manifest, roost_plugins
+
+	#
+	# Call provider's bootstrap method first.
+	#
+	actions.push (node_name, roost_manifest, roost_plugins, callback) ->
+		provider.bootstrap node_name, (err) ->
+			return callback err if err
+			return callback null, node_name, roost_manifest, roost_plugins
+
+	#
+	# Obtain shell spec.
+	#
+	actions.push (node_name, roost_manifest, roost_plugins, callback) ->
+		provider.shell_spec node_name, (err, spec) ->
+			return callback err if err
+			return callback null, node_name, roost_manifest, roost_plugins, spec
+
+	#
+	# Expose nodes to each other by using roost as a provisioner.
+	#
+	actions.push (node_name, roost_manifest, roost_plugins, spec, callback) ->
+		roost_manifest.bootstrap ?= []
+		roost_manifest.bootstrap.push 'sudo mkdir -p /etc/vortex/nodes/'
+
+		obtain_status = (node_name, callback) ->
+			provider.status node_name, (err, state, address) ->
+				return callback err if err
+				return callback null, {node_name: node_name, address: address}
+
+		async.map Object.keys(manifest.nodes), obtain_status, (err, results) ->
+			return callback err if err
+
+			for result in results
+				if result.node_name == node_name
+					continue
+
+				if not result.address
+					logsmith.error "node #{node_name} does not expose address"
+
+					continue
+
+				a = shell_quote.quote([result.address])
+				f = shell_quote.quote(["/etc/vortex/nodes/#{result.node_name}"])
+
+				roost_manifest.bootstrap.unshift "echo #{a} | sudo tee #{f}"
+
+			return callback null, node_name, roost_manifest, roost_plugins, spec
+
+	#
+	# Setup the roost target and invoke roost.
+	#
+	actions.push (node_name, roost_manifest, roost_plugins, spec, callback) ->
+		try
+			roost_target = roost.targets.create spec, roost_manifest
+		catch e
+			return callback e
+
+		roost_opt = options: {}, argv: []
+		roost_opt.options.dry = opt.options.dry if opt.options.dry?
+
+		roost.engine.launch roost_opt, roost_manifest, roost_plugins, roost_target, callback
+
+	#
+	# Invoke actions for each node.
+	#
+	process_node = (node_name, callback) ->
+		logsmith.info "provision node #{node_name}"
+
+		current_actions = [((callback) -> callback null, node_name), actions...]
+
+		async.waterfall current_actions, callback
+
+	async.eachSeries node_names, process_node, callback
+
+# ---
+
 exports.up = (opt, manifest, provider, node_names, callback) ->
 	###
 	This action will bring up a node by first booting it and than starting the provisioning process.
 	###
+
+	#
+	# Boot and provision nodes.
+	#
 	process_node = (node_name, callback) ->
 		provider.status node_name, (err, state, address) ->
 			return callback err if err
@@ -519,6 +344,10 @@
 	###
 	This action will bring down a node. This is esentially a wrapper around the halt action.
 	###
+
+	#
+	# Halt nodes.
+	#
 	process_node = (node_name, callback) ->
 		provider.status node_name, (err, state, address) ->
 			return callback err if err
@@ -536,16 +365,25 @@
 	###
 	actions = []

+	#
+	# Invoke action down.
+	#
 	actions.push (node_name, callback) ->
 		exports.down opt, manifest, provider, [node_name], (err) ->
 			return callback err if err
 			return callback null, node_name

+	#
+	# Invoke action up.
+	#
 	actions.push (node_name, callback) ->
 		exports.up opt, manifest, provider, [node_name], (err) ->
 			return callback err if err
 			return callback null, node_name

+	#
+	# Invoke actions for each node.
+	#
 	process_node = (node_name, callback) ->
 		logsmith.verbose "reload node #{node_name}"

@@ -557,34 +395,24 @@

 # ---

-exports.shellspec = (opt, manifest, provider, node_names, callback) ->
-	###
-	This action output the shell spec of a node using the selected provider.
-	###
-	process_node = (node_name, callback) ->
-		provider.shell_spec node_name, (err, spec) ->
-			return callback err if err
-
-			logsmith.info node_name, '->', spec
-
-			return callback null, spec
-
-	async.eachSeries node_names, process_node, callback
-
-# ---
-
 exports.shell =  (opt, manifest, provider, node_names, callback) ->
 	###
 	This action start a shell or executes a command on nodes.
 	###
 	actions = []

+	#
+	# Obtain shell spec.
+	#
 	actions.push (node_name, callback) ->
 		provider.shell_spec node_name, (err, spec) ->
 			return callback err if err
 			return callback new Error "unsupported shell spec #{spec}" if not spec.match /^ssh:/i
 			return callback null, spec

+	#
+	# Start shell or execute a command.
+	#
 	actions.push (spec, callback) ->
 		ssh = new shell.Ssh spec, manifest
 		command = opt.argv.slice opt.argv.indexOf('--') + 1
@@ -603,6 +431,9 @@
 			return callback err if err
 			return callback null

+	#
+	# Invoke actions for each node.
+	#
 	process_node = (node_name, callback) ->
 		logsmith.info "shell into node #{node_name}"


off cancel event

off cancel event

--- lib/build/build-list-view.coffee
+++ lib/build/build-list-view.coffee
@@ -7,6 +7,7 @@
   initialize: ->
     super
     @filterEditorView.off 'blur'
+    @off 'core:cancel'

   getFilterKey: -> 'id'


:lipstick:

:lipstick:

--- lib/github-file.coffee
+++ lib/github-file.coffee
@@ -11,7 +11,7 @@

   # Internal
   constructor: (@filePath) ->
-    @repo = atom.project.getRepositories()[0]
+    [@repo] = atom.project.getRepositories()

   # Public
   open: (lineRange) ->


fix bug

fix bug

--- lib/carte/client/views/card.cjsx
+++ lib/carte/client/views/card.cjsx
@@ -30,7 +30,7 @@
     console.log 'Card: render'

-

+

             {
               if @props.card.get('focused')

@@ -52,7 +52,7 @@

-

+


Add spec for textarea element

Add spec for textarea element

--- test/spec/jquery.defaultValue.spec.coffee
+++ test/spec/jquery.defaultValue.spec.coffee
@@ -459,6 +459,38 @@
         expect($(this).defaultValue()).toBe results[i]

+  describe 'textarea element', ->
+    it 'should return undefined if defaultValue is not set', ->
+      @$fixture = setFixtures '<textarea></textarea>'
+      @$e = @$fixture.find 'textarea'
+      expect(@$e.eq(0).defaultValue()).toBeUndefined
+
+    it 'should return defaultValue correctly if defaultValue is set', ->
+      @$fixture = setFixtures '<textarea>123</textarea>'
+      @$e = @$fixture.find 'textarea'
+      expect(@$e.eq(0).defaultValue()).toEqual '123'
+
+    it 'recognizes space and linebreak as defaulValue', ->
+      @$fixture = setFixtures '<textarea> \n </textarea>'
+      @$e = @$fixture.find 'textarea'
+      expect(@$e.eq(0).defaultValue()).toEqual ' \n '
+
+    it 'should update defaultValue correctly', ->
+      @$fixture = setFixtures '<textarea>v1</textarea><textarea>v2</textarea>'
+      @$e = @$fixture.find 'textarea'
+      @$e.defaultValue 'updated_value'
+      expect(@$e.eq(0).defaultValue()).toEqual 'updated_value'
+      expect(@$e.eq(1).defaultValue()).toEqual 'updated_value'
+
+    it 'should sync defaultValue correctly', ->
+      @$fixture = setFixtures '<textarea>v1</textarea><textarea>v2</textarea>'
+      @$e = @$fixture.find 'textarea'
+      @$e.eq(0).val 'updated_value1'
+      @$e.eq(1).val 'updated_value2'
+      @$e.syncDefaultValue()
+      expect(@$e.eq(0).defaultValue()).toEqual 'updated_value1'
+      expect(@$e.eq(1).defaultValue()).toEqual 'updated_value2'
+
   describe 'non-html form element', ->
     beforeEach ->
       @$fixture = setFixtures '

'


Fix behavior of text wrapping with x/y without width. Use options.lineBreak = false to disable linebreaking

--- lib/mixins/text.coffee
+++ lib/mixins/text.coffee
@@ -119,7 +119,7 @@
             @y = y or @y

         # wrap to margins if no x or y position passed
-        else
+        unless options.lineBreak is false
             margins = @page.margins
             options.width ?= @page.width - @x - margins.right
             options.height ?= @page.height - @y - margins.bottom


Flush old entries from commit message file

--- lib/models/repo.coffee
+++ lib/models/repo.coffee
@@ -51,7 +51,10 @@
     git.incrementTaskCounter()
     if atom.config.get("atomatigit.pre_commit_hook") != ""
       atom.workspaceView.trigger(atom.config.get("atomatigit.pre_commit_hook"))
-    new File @commitMessagePath()
+
+    file = new File @commitMessagePath()
+    file.write('')
+
     editor = atom.workspace.open(@commitMessagePath(), {changeFocus: true})
     git.statusFull @writeCommitMessage


Opening new files improved

Opening new files improved

Added a small improvement where newly opened files, when currently in a
pinned tab, will open after the last pinned tabs. This is a first step
towards ensuring that pinned tabs are always up front.

--- lib/pinned-tabs.coffee
+++ lib/pinned-tabs.coffee
@@ -25,7 +25,8 @@

     activate: (state) ->
         @setCommands()
-        @setObservers()
+        @configObservers()
+        @observers()

         # Recover the serialized session or start a new serializable state.
         @PinnedTabsState =
@@ -73,7 +74,7 @@
         @subscriptions.add atom.commands.add 'atom-workspace', 'pinned-tabs:pin-selected': => @pinSelected()

     # Add an event listener for when the value of the settings are changed.
-    setObservers: ->
+    configObservers: ->
         body = document.querySelector 'body'
         atom.config.observe 'pinned-tabs.disableTabAnimation', (newValue) ->
             if newValue
@@ -92,6 +93,15 @@
             else
                 body.classList.add 'pinned-icons-colorless'

+    #
+    observers: ->
+        self = this # This object has to be stored in self because the callback function will create its own 'this'
+        atom.workspace.onDidAddPaneItem (event) ->
+            setTimeout (->
+                r = self.getPaneInformation document.querySelector('.tab-bar .tab.active'), event.index
+                r.pane.moveItem r.item, r.index
+            ), 1
+

     # Method to pin the active tab.
     pinActive: ->
@@ -103,44 +113,57 @@

     # Method that pins/unpins a tab given its element.
     pin: (e) ->
-        # Get necessary DOM elements
-        tabbar = e.parentNode
-        pane = tabbar.parentNode
-        axis = pane.parentNode
-
-        # Get the index of the selected tab and the
-        # corresponding pane.
-        selectedIndex = Array.prototype.indexOf.call tabbar.children, e
-        paneIndex = Array.prototype.indexOf.call axis.children, pane
+        #
+        r = @getPaneInformation e

         # Calculate the new index for this tab based
         # on the amount of pinned tabs within this pane.
-        newIndex = e.parentNode.querySelectorAll('.pinned').length
         if e.classList.contains 'pinned'
             # If the element has the element 'pinned', it
             # is currently being unpinned. So the new index
             # is one off when look at the amount of pinned
             # tabs, because it actually includes the tab
             # that is being unpinned.
-            newIndex -= 1
+            r.index -= 1

             # Removed one pinned tab from the state key for this pane.
-            @PinnedTabsState.data[paneIndex] -= 1
+            @PinnedTabsState.data[r.paneIndex] -= 1
         else
             # Initialize the state kye for this pane if needed.
-            @PinnedTabsState.data[paneIndex] = 0 if @PinnedTabsState.data[paneIndex] == undefined
+            @PinnedTabsState.data[r.paneIndex] = 0 if @PinnedTabsState.data[r.paneIndex] == undefined

             # Add one pinned tab from the state key for this pane.
-            @PinnedTabsState.data[paneIndex] += 1
+            @PinnedTabsState.data[r.paneIndex] += 1

-        # Actually move the item to its new index.
-        #pane = atom.workspace.getActivePane()
-        pane = atom.workspace.getPanes()[paneIndex / 2]
-        item = pane.itemAtIndex selectedIndex
-        pane.moveItem item, newIndex
+        #
+        r.pane.moveItem r.item, r.index

         # Finally, toggle the 'pinned' class on the tab after a
         # timout of 1 millisecond. This will ensure the animation
         # of pinning the tab will run.
         callback = -> e.classList.toggle 'pinned'
         setTimeout callback, 1
+
+
+    #
+    getPaneInformation: (e, i) ->
+        #
+        tabbar = e.parentNode
+        pane = tabbar.parentNode
+        axis = pane.parentNode
+
+        #
+        selectedIndex = i || Array.prototype.indexOf.call tabbar.children, e
+        paneIndex = Array.prototype.indexOf.call axis.children, pane
+        newIndex = e.parentNode.querySelectorAll('.pinned').length
+
+        #
+        pane = atom.workspace.getPanes()[paneIndex / 2]
+        item = pane.itemAtIndex selectedIndex
+
+        return {
+            index: newIndex,
+            pane: pane,
+            paneIndex: paneIndex
+            item: item
+        };


remove the redundant bower dependency declaration

--- library/fringes/tracker.coffee
+++ library/fringes/tracker.coffee
@@ -41,13 +41,6 @@
 # the connection event information, text and color coded, usually.
 module.exports.DuplexTracker = class DuplexTracker extends Behavior

-    # This block here defines a set of Bower dependencies that are
-    # required by the client site part of the code that constitutes
-    # this service or compound. Dependencies can be restricted to a
-    # certain version and also they can have customized entrypoint.
-    # Refer to `BowerSupport` class implementation for information.
-    @bower "toastr#2.0.x"
-
     # Make the current service available to the specified roles of
     # authenticated accounts, utilizing the `Policies` component.
     # The signature is either a role name (as a string) or object